@@ -15,7 +15,13 @@
#ifndef QEMU_MIGRATION_SNAPSHOT_H
#define QEMU_MIGRATION_SNAPSHOT_H
-int save_snapshot(const char *name, Error **errp);
-int load_snapshot(const char *name, Error **errp);
+#include "qapi/qapi-builtin-types.h"
+
+int save_snapshot(const char *name,
+ const char *vmstate, strList *devices,
+ Error **errp);
+int load_snapshot(const char *name,
+ const char *vmstate, strList *devices,
+ Error **errp);
#endif
@@ -43,6 +43,8 @@
#include "qapi/error.h"
#include "qapi/qapi-commands-migration.h"
#include "qapi/qapi-commands-misc.h"
+#include "qapi/clone-visitor.h"
+#include "qapi/qapi-builtin-visit.h"
#include "qapi/qmp/qerror.h"
#include "qemu/error-report.h"
#include "sysemu/cpus.h"
@@ -2631,7 +2633,8 @@ int qemu_load_device_state(QEMUFile *f)
return 0;
}
-int save_snapshot(const char *name, Error **errp)
+int save_snapshot(const char *name, const char *vmstate,
+ strList *devices, Error **errp)
{
BlockDriverState *bs;
QEMUSnapshotInfo sn1, *sn = &sn1, old_sn1, *old_sn = &old_sn1;
@@ -2653,18 +2656,18 @@ int save_snapshot(const char *name, Error **errp)
return ret;
}
- if (!bdrv_all_can_snapshot(NULL, errp)) {
+ if (!bdrv_all_can_snapshot(devices, errp)) {
return ret;
}
/* Delete old snapshots of the same name */
if (name) {
- if (bdrv_all_delete_snapshot(name, NULL, errp) < 0) {
+ if (bdrv_all_delete_snapshot(name, devices, errp) < 0) {
return ret;
}
}
- bs = bdrv_all_find_vmstate_bs(NULL, NULL, errp);
+ bs = bdrv_all_find_vmstate_bs(vmstate, devices, errp);
if (bs == NULL) {
return ret;
}
@@ -2730,7 +2733,7 @@ int save_snapshot(const char *name, Error **errp)
aio_context_release(aio_context);
aio_context = NULL;
- ret = bdrv_all_create_snapshot(sn, bs, vm_state_size, NULL, errp);
+ ret = bdrv_all_create_snapshot(sn, bs, vm_state_size, devices, errp);
if (ret < 0) {
goto the_end;
}
@@ -2831,7 +2834,8 @@ void qmp_xen_load_devices_state(const char *filename, Error **errp)
migration_incoming_state_destroy();
}
-int load_snapshot(const char *name, Error **errp)
+int load_snapshot(const char *name, const char *vmstate,
+ strList *devices, Error **errp)
{
BlockDriverState *bs_vm_state;
QEMUSnapshotInfo sn;
@@ -2846,15 +2850,15 @@ int load_snapshot(const char *name, Error **errp)
return -1;
}
- if (!bdrv_all_can_snapshot(NULL, errp)) {
+ if (!bdrv_all_can_snapshot(devices, errp)) {
return -1;
}
- ret = bdrv_all_find_snapshot(name, NULL, errp);
+ ret = bdrv_all_find_snapshot(name, devices, errp);
if (ret < 0) {
return -1;
}
- bs_vm_state = bdrv_all_find_vmstate_bs(NULL, NULL, errp);
+ bs_vm_state = bdrv_all_find_vmstate_bs(vmstate, devices, errp);
if (!bs_vm_state) {
return -1;
}
@@ -2875,7 +2879,7 @@ int load_snapshot(const char *name, Error **errp)
/* Flush all IO requests so they don't interfere with the new state. */
bdrv_drain_all_begin();
- ret = bdrv_all_goto_snapshot(name, NULL, errp);
+ ret = bdrv_all_goto_snapshot(name, devices, errp);
if (ret < 0) {
goto err_drain;
}
@@ -2936,3 +2940,151 @@ bool vmstate_check_only_migratable(const VMStateDescription *vmsd)
return !(vmsd && vmsd->unmigratable);
}
+
+typedef struct SnapshotJob {
+ Job common;
+ char *tag;
+ char *vmstate;
+ strList *devices;
+} SnapshotJob;
+
+static void qmp_snapshot_job_free(SnapshotJob *s)
+{
+ g_free(s->tag);
+ g_free(s->vmstate);
+ qapi_free_strList(s->devices);
+}
+
+static int coroutine_fn snapshot_load_job_run(Job *job, Error **errp)
+{
+ SnapshotJob *s = container_of(job, SnapshotJob, common);
+ int ret;
+ int saved_vm_running;
+
+ job_progress_set_remaining(&s->common, 1);
+
+ saved_vm_running = runstate_is_running();
+ vm_stop(RUN_STATE_RESTORE_VM);
+
+ ret = load_snapshot(s->tag, s->vmstate, s->devices, errp);
+ if (ret == 0 && saved_vm_running) {
+ vm_start();
+ }
+
+ job_progress_update(&s->common, 1);
+
+ qmp_snapshot_job_free(s);
+
+ return ret;
+}
+
+static int coroutine_fn snapshot_save_job_run(Job *job, Error **errp)
+{
+ SnapshotJob *s = container_of(job, SnapshotJob, common);
+ int ret;
+
+ job_progress_set_remaining(&s->common, 1);
+ ret = save_snapshot(s->tag, s->vmstate, s->devices, errp);
+ job_progress_update(&s->common, 1);
+
+ qmp_snapshot_job_free(s);
+
+ return ret;
+}
+
+static int coroutine_fn snapshot_delete_job_run(Job *job, Error **errp)
+{
+ SnapshotJob *s = container_of(job, SnapshotJob, common);
+ int ret;
+
+ job_progress_set_remaining(&s->common, 1);
+ ret = bdrv_all_delete_snapshot(s->tag, s->devices, errp);
+ job_progress_update(&s->common, 1);
+
+ qmp_snapshot_job_free(s);
+
+ return ret;
+}
+
+static const JobDriver snapshot_load_job_driver = {
+ .instance_size = sizeof(SnapshotJob),
+ .job_type = JOB_TYPE_SNAPSHOT_LOAD,
+ .run = snapshot_load_job_run,
+};
+
+static const JobDriver snapshot_save_job_driver = {
+ .instance_size = sizeof(SnapshotJob),
+ .job_type = JOB_TYPE_SNAPSHOT_SAVE,
+ .run = snapshot_save_job_run,
+};
+
+static const JobDriver snapshot_delete_job_driver = {
+ .instance_size = sizeof(SnapshotJob),
+ .job_type = JOB_TYPE_SNAPSHOT_DELETE,
+ .run = snapshot_delete_job_run,
+};
+
+
+void qmp_snapshot_save(const char *job_id,
+ const char *tag,
+ bool has_vmstate, const char *vmstate,
+ bool has_devices, strList *devices,
+ Error **errp)
+{
+ SnapshotJob *s;
+
+ s = job_create(job_id, &snapshot_save_job_driver, NULL,
+ qemu_get_aio_context(), JOB_MANUAL_DISMISS,
+ NULL, NULL, errp);
+ if (!s) {
+ return;
+ }
+
+ s->tag = g_strdup(tag);
+ s->vmstate = has_vmstate ? g_strdup(vmstate) : NULL;
+ s->devices = has_devices ? QAPI_CLONE(strList, devices) : NULL;
+
+ job_start(&s->common);
+}
+
+void qmp_snapshot_load(const char *job_id,
+ const char *tag,
+ bool has_vmstate, const char *vmstate,
+ bool has_devices, strList *devices,
+ Error **errp)
+{
+ SnapshotJob *s;
+
+ s = job_create(job_id, &snapshot_load_job_driver, NULL,
+ qemu_get_aio_context(), JOB_MANUAL_DISMISS,
+ NULL, NULL, errp);
+ if (!s) {
+ return;
+ }
+
+ s->tag = g_strdup(tag);
+ s->vmstate = has_vmstate ? g_strdup(vmstate) : NULL;
+ s->devices = has_devices ? QAPI_CLONE(strList, devices) : NULL;
+
+ job_start(&s->common);
+}
+
+void qmp_snapshot_delete(const char *job_id,
+ const char *tag,
+ bool has_devices, strList *devices,
+ Error **errp)
+{
+ SnapshotJob *s;
+
+ s = job_create(job_id, &snapshot_delete_job_driver, NULL,
+ qemu_get_aio_context(), JOB_MANUAL_DISMISS,
+ NULL, NULL, errp);
+ if (!s) {
+ return;
+ }
+
+ s->tag = g_strdup(tag);
+ s->devices = has_devices ? QAPI_CLONE(strList, devices) : NULL;
+
+ job_start(&s->common);
+}
@@ -1095,7 +1095,7 @@ void hmp_loadvm(Monitor *mon, const QDict *qdict)
vm_stop(RUN_STATE_RESTORE_VM);
- if (load_snapshot(name, &err) == 0 && saved_vm_running) {
+ if (load_snapshot(name, NULL, NULL, &err) == 0 && saved_vm_running) {
vm_start();
}
hmp_handle_error(mon, err);
@@ -1105,7 +1105,7 @@ void hmp_savevm(Monitor *mon, const QDict *qdict)
{
Error *err = NULL;
- save_snapshot(qdict_get_try_str(qdict, "name"), &err);
+ save_snapshot(qdict_get_try_str(qdict, "name"), NULL, NULL, &err);
hmp_handle_error(mon, err);
}
@@ -21,10 +21,17 @@
#
# @amend: image options amend job type, see "x-blockdev-amend" (since 5.1)
#
+# @snapshot-load: snapshot load job type, see "loadvm" (since 5.2)
+#
+# @snapshot-save: snapshot save job type, see "savevm" (since 5.2)
+#
+# @snapshot-delete: snapshot delete job type, see "delvm" (since 5.2)
+#
# Since: 1.7
##
{ 'enum': 'JobType',
- 'data': ['commit', 'stream', 'mirror', 'backup', 'create', 'amend'] }
+ 'data': ['commit', 'stream', 'mirror', 'backup', 'create', 'amend',
+ 'snapshot-load', 'snapshot-save', 'snapshot-delete'] }
##
# @JobStatus:
@@ -1621,3 +1621,115 @@
##
{ 'event': 'UNPLUG_PRIMARY',
'data': { 'device-id': 'str' } }
+
+##
+# @snapshot-save:
+#
+# Save a VM snapshot
+#
+# @job-id: identifier for the newly created job
+# @tag: name of the snapshot to create. If it already
+# exists it will be replaced.
+# @devices: list of block device node names to save a snapshot to
+# @vmstate: block device node name to save vmstate to
+#
+# Applications should not assume that the snapshot save is complete
+# when this command returns. Completion is indicated by the job
+# status. Clients can wait for the JOB_STATUS_CHANGE event.
+#
+# Note that the VM CPUs will be paused during the time it takes to
+# save the snapshot
+#
+# Returns: nothing
+#
+# Example:
+#
+# -> { "execute": "snapshot-save",
+# "data": {
+# "job-id": "snapsave0",
+# "tag": "my-snap",
+# "vmstate": "disk0",
+# "devices": ["disk0", "disk1"]
+# }
+# }
+# <- { "return": { } }
+#
+# Since: 5.2
+##
+{ 'command': 'snapshot-save',
+ 'data': { 'job-id': 'str',
+ 'tag': 'str',
+ '*vmstate': 'str',
+ '*devices': ['str'] } }
+
+##
+# @snapshot-load:
+#
+# Load a VM snapshot
+#
+# @job-id: identifier for the newly created job
+# @tag: name of the snapshot to load.
+# @devices: list of block device node names to load a snapshot from
+# @vmstate: block device node name to load vmstate from
+#
+# Applications should not assume that the snapshot load is complete
+# when this command returns. Completion is indicated by the job
+# status. Clients can wait for the JOB_STATUS_CHANGE event.
+#
+# Returns: nothing
+#
+# Example:
+#
+# -> { "execute": "snapshot-load",
+# "data": {
+# "job-id": "snapload0",
+# "tag": "my-snap",
+# "vmstate": "disk0",
+# "devices": ["disk0", "disk1"]
+# }
+# }
+# <- { "return": { } }
+#
+# Since: 5.2
+##
+{ 'command': 'snapshot-load',
+ 'data': { 'job-id': 'str',
+ 'tag': 'str',
+ '*vmstate': 'str',
+ '*devices': ['str'] } }
+
+##
+# @snapshot-delete:
+#
+# Delete a VM snapshot
+#
+# @job-id: identifier for the newly created job
+# @tag: name of the snapshot to delete.
+# @devices: list of block device node names to delete a snapshot from
+#
+# Applications should not assume that the snapshot load is complete
+# when this command returns. Completion is indicated by the job
+# status. Clients can wait for the JOB_STATUS_CHANGE event.
+#
+# Note that the VM CPUs will be paused during the time it takes to
+# delete the snapshot
+#
+# Returns: nothing
+#
+# Example:
+#
+# -> { "execute": "snapshot-delete",
+# "data": {
+# "job-id": "snapdelete0",
+# "tag": "my-snap",
+# "devices": ["disk0", "disk1"]
+# }
+# }
+# <- { "return": { } }
+#
+# Since: 5.2
+##
+{ 'command': 'snapshot-delete',
+ 'data': { 'job-id': 'str',
+ 'tag': 'str',
+ '*devices': ['str'] } }
@@ -77,13 +77,13 @@ void replay_vmstate_init(void)
if (replay_snapshot) {
if (replay_mode == REPLAY_MODE_RECORD) {
- if (save_snapshot(replay_snapshot, &err) != 0) {
+ if (save_snapshot(replay_snapshot, NULL, NULL, &err) != 0) {
error_report_err(err);
error_report("Could not create snapshot for icount record");
exit(1);
}
} else if (replay_mode == REPLAY_MODE_PLAY) {
- if (load_snapshot(replay_snapshot, &err) != 0) {
+ if (load_snapshot(replay_snapshot, NULL, NULL, &err) != 0) {
error_report_err(err);
error_report("Could not load snapshot for icount replay");
exit(1);
@@ -4456,7 +4456,7 @@ void qemu_init(int argc, char **argv, char **envp)
register_global_state();
if (loadvm) {
Error *local_err = NULL;
- if (load_snapshot(loadvm, &local_err) < 0) {
+ if (load_snapshot(loadvm, NULL, NULL, &local_err) < 0) {
error_report_err(local_err);
autostart = 0;
exit(1);
new file mode 100755
@@ -0,0 +1,125 @@
+#!/usr/bin/env bash
+#
+# Test which nodes are involved in internal snapshots
+#
+# Copyright (C) 2019 Red Hat, Inc.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+
+# creator
+owner=berrange@redhat.com
+
+seq=`basename $0`
+echo "QA output created by $seq"
+
+status=1 # failure is the default!
+
+_cleanup()
+{
+ _cleanup_test_img
+ rm -f "$SOCK_DIR/nbd"
+}
+trap "_cleanup; exit \$status" 0 1 2 3 15
+
+# get standard environment, filters and checks
+. ./common.rc
+. ./common.filter
+
+_supported_fmt qcow2
+_supported_proto file
+_supported_os Linux
+_require_drivers copy-on-read
+
+# Internal snapshots are (currently) impossible with refcount_bits=1,
+# and generally impossible with external data files
+_unsupported_imgopts 'refcount_bits=1[^0-9]' data_file
+
+_require_devices virtio-blk
+
+do_run_qemu()
+{
+ echo Testing: "$@"
+ (
+ if ! test -t 0; then
+ while read cmd; do
+ echo $cmd
+ done
+ fi
+ echo quit
+ ) | $QEMU -nographic -qmp stdio -nodefaults "$@"
+ echo
+}
+
+run_qemu()
+{
+ do_run_qemu "$@" 2>&1 | _filter_testdir | _filter_qemu | _filter_hmp |
+ _filter_generated_node_ids | _filter_imgfmt | _filter_vmstate_size
+}
+
+size=128M
+
+run_test()
+{
+ if [ -n "$BACKING_FILE" ]; then
+ _make_test_img -b "$BACKING_FILE" -F $IMGFMT $size
+ else
+ _make_test_img $size
+ fi
+ run_qemu "$@" <<EOF | _filter_date
+{ "execute": "qmp_capabilities" }
+{ "execute": "snapshot-save",
+ "arguments": {
+ "job-id": "snapsave0",
+ "tag": "snap0",
+ "devices": ["disk1"]
+ } }
+{ "execute": "query-jobs" }
+{ "execute": "query-named-block-nodes" }
+{ "execute": "snapshot-load",
+ "arguments": {
+ "job-id": "snapload0",
+ "tag": "snap0",
+ "devices": ["disk1"]
+ } }
+{ "execute": "query-jobs" }
+{ "execute": "query-named-block-nodes" }
+{ "execute": "snapshot-delete",
+ "arguments": {
+ "job-id": "snapdel0",
+ "tag": "snap0",
+ "devices": ["disk1"]
+ } }
+{ "execute": "query-jobs" }
+{ "execute": "snapshot-delete",
+ "arguments": {
+ "job-id": "snapdel1",
+ "tag": "snap1",
+ "devices": ["disk1"]
+ } }
+{ "execute": "query-jobs" }
+{ "execute": "query-named-block-nodes" }
+{ "execute": "quit" }
+EOF
+}
+
+echo "Basic test"
+
+run_test -blockdev "{\"driver\":\"file\",\"filename\":\"$TEST_IMG\",\"node-name\":\"disk0\"}" \
+ -blockdev "{\"driver\":\"qcow2\",\"file\":\"disk0\",\"node-name\":\"disk1\"}"
+
+# success, all done
+echo "*** done"
+rm -f $seq.full
+status=0
new file mode 100644
@@ -290,6 +290,7 @@
277 rw quick
279 rw backing quick
280 rw migration quick
+310 rw quick
281 rw quick
282 rw img quick
283 auto quick
savevm, loadvm and delvm are some of the few HMP commands that have never been converted to use QMP. The primary reason for this lack of conversion is that they block execution of the thread for as long as they run. Despite this downside, however, libvirt and applications using libvirt have used these commands for as long as QMP has existed, via the "human-monitor-command" passthrough command. IOW, while it is clearly desirable to be able to fix the blocking problem, this is not an immediate obstacle to real world usage. Meanwhile there is a need for other features which involve adding new parameters to the commands. This is possible with HMP passthrough, but it provides no reliable way for apps to introspect features, so using QAPI modelling is highly desirable. This patch thus introduces new snapshot-{load,save,delete} commands to QMP that are intended to replace the old HMP counterparts. The new commands are given different names, because they will be using the new QEMU job framework and thus will have diverging behaviour from the HMP originals. It would thus be misleading to keep the same name. While this design uses the generic job framework, the current impl is still blocking. The intention that the blocking problem is fixed later. None the less applications using these new commands should assume that they are asynchronous and thus wait for the job status change event to indicate completion. Signed-off-by: Daniel P. Berrangé <berrange@redhat.com> --- include/migration/snapshot.h | 10 +- migration/savevm.c | 172 +++++++++++++++++++++++++++++++++-- monitor/hmp-cmds.c | 4 +- qapi/job.json | 9 +- qapi/migration.json | 112 +++++++++++++++++++++++ replay/replay-snapshot.c | 4 +- softmmu/vl.c | 2 +- tests/qemu-iotests/310 | 125 +++++++++++++++++++++++++ tests/qemu-iotests/310.out | 0 tests/qemu-iotests/group | 1 + 10 files changed, 421 insertions(+), 18 deletions(-) create mode 100755 tests/qemu-iotests/310 create mode 100644 tests/qemu-iotests/310.out