migration: introduce snapshot-{save, load, delete} QMP commands
savevm, loadvm and delvm are some of the few HMP commands that have never been converted to use QMP. The reasons for the lack of conversion are that they blocked execution of the event thread, and the semantics around choice of disks were ill-defined. 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 problems, they are not a blocker to all 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. In addition to using the job framework, the new commands require the caller to be explicit about all the block device nodes used in the snapshot operations, with no built-in default heuristics in use. Note that the existing "query-named-block-nodes" can be used to query what snapshots currently exist for block nodes. Acked-by: Markus Armbruster <armbru@redhat.com> Signed-off-by: Daniel P. Berrangé <berrange@redhat.com> Message-Id: <20210204124834.774401-13-berrange@redhat.com> Signed-off-by: Dr. David Alan Gilbert <dgilbert@redhat.com> dgilbert: removed tests for now, the output ordering isn't deterministic
This commit is contained in:
parent
458598e6a5
commit
0f0d83a456
@ -3112,3 +3112,187 @@ bool vmstate_check_only_migratable(const VMStateDescription *vmsd)
|
||||
|
||||
return !(vmsd && vmsd->unmigratable);
|
||||
}
|
||||
|
||||
typedef struct SnapshotJob {
|
||||
Job common;
|
||||
char *tag;
|
||||
char *vmstate;
|
||||
strList *devices;
|
||||
Coroutine *co;
|
||||
Error **errp;
|
||||
bool ret;
|
||||
} SnapshotJob;
|
||||
|
||||
static void qmp_snapshot_job_free(SnapshotJob *s)
|
||||
{
|
||||
g_free(s->tag);
|
||||
g_free(s->vmstate);
|
||||
qapi_free_strList(s->devices);
|
||||
}
|
||||
|
||||
|
||||
static void snapshot_load_job_bh(void *opaque)
|
||||
{
|
||||
Job *job = opaque;
|
||||
SnapshotJob *s = container_of(job, SnapshotJob, common);
|
||||
int orig_vm_running;
|
||||
|
||||
job_progress_set_remaining(&s->common, 1);
|
||||
|
||||
orig_vm_running = runstate_is_running();
|
||||
vm_stop(RUN_STATE_RESTORE_VM);
|
||||
|
||||
s->ret = load_snapshot(s->tag, s->vmstate, true, s->devices, s->errp);
|
||||
if (s->ret && orig_vm_running) {
|
||||
vm_start();
|
||||
}
|
||||
|
||||
job_progress_update(&s->common, 1);
|
||||
|
||||
qmp_snapshot_job_free(s);
|
||||
aio_co_wake(s->co);
|
||||
}
|
||||
|
||||
static void snapshot_save_job_bh(void *opaque)
|
||||
{
|
||||
Job *job = opaque;
|
||||
SnapshotJob *s = container_of(job, SnapshotJob, common);
|
||||
|
||||
job_progress_set_remaining(&s->common, 1);
|
||||
s->ret = save_snapshot(s->tag, false, s->vmstate,
|
||||
true, s->devices, s->errp);
|
||||
job_progress_update(&s->common, 1);
|
||||
|
||||
qmp_snapshot_job_free(s);
|
||||
aio_co_wake(s->co);
|
||||
}
|
||||
|
||||
static void snapshot_delete_job_bh(void *opaque)
|
||||
{
|
||||
Job *job = opaque;
|
||||
SnapshotJob *s = container_of(job, SnapshotJob, common);
|
||||
|
||||
job_progress_set_remaining(&s->common, 1);
|
||||
s->ret = delete_snapshot(s->tag, true, s->devices, s->errp);
|
||||
job_progress_update(&s->common, 1);
|
||||
|
||||
qmp_snapshot_job_free(s);
|
||||
aio_co_wake(s->co);
|
||||
}
|
||||
|
||||
static int coroutine_fn snapshot_save_job_run(Job *job, Error **errp)
|
||||
{
|
||||
SnapshotJob *s = container_of(job, SnapshotJob, common);
|
||||
s->errp = errp;
|
||||
s->co = qemu_coroutine_self();
|
||||
aio_bh_schedule_oneshot(qemu_get_aio_context(),
|
||||
snapshot_save_job_bh, job);
|
||||
qemu_coroutine_yield();
|
||||
return s->ret ? 0 : -1;
|
||||
}
|
||||
|
||||
static int coroutine_fn snapshot_load_job_run(Job *job, Error **errp)
|
||||
{
|
||||
SnapshotJob *s = container_of(job, SnapshotJob, common);
|
||||
s->errp = errp;
|
||||
s->co = qemu_coroutine_self();
|
||||
aio_bh_schedule_oneshot(qemu_get_aio_context(),
|
||||
snapshot_load_job_bh, job);
|
||||
qemu_coroutine_yield();
|
||||
return s->ret ? 0 : -1;
|
||||
}
|
||||
|
||||
static int coroutine_fn snapshot_delete_job_run(Job *job, Error **errp)
|
||||
{
|
||||
SnapshotJob *s = container_of(job, SnapshotJob, common);
|
||||
s->errp = errp;
|
||||
s->co = qemu_coroutine_self();
|
||||
aio_bh_schedule_oneshot(qemu_get_aio_context(),
|
||||
snapshot_delete_job_bh, job);
|
||||
qemu_coroutine_yield();
|
||||
return s->ret ? 0 : -1;
|
||||
}
|
||||
|
||||
|
||||
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,
|
||||
const char *vmstate,
|
||||
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 = g_strdup(vmstate);
|
||||
s->devices = QAPI_CLONE(strList, devices);
|
||||
|
||||
job_start(&s->common);
|
||||
}
|
||||
|
||||
void qmp_snapshot_load(const char *job_id,
|
||||
const char *tag,
|
||||
const char *vmstate,
|
||||
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 = g_strdup(vmstate);
|
||||
s->devices = QAPI_CLONE(strList, devices);
|
||||
|
||||
job_start(&s->common);
|
||||
}
|
||||
|
||||
void qmp_snapshot_delete(const char *job_id,
|
||||
const char *tag,
|
||||
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 = QAPI_CLONE(strList, devices);
|
||||
|
||||
job_start(&s->common);
|
||||
}
|
||||
|
@ -22,10 +22,17 @@
|
||||
#
|
||||
# @amend: image options amend job type, see "x-blockdev-amend" (since 5.1)
|
||||
#
|
||||
# @snapshot-load: snapshot load job type, see "snapshot-load" (since 6.0)
|
||||
#
|
||||
# @snapshot-save: snapshot save job type, see "snapshot-save" (since 6.0)
|
||||
#
|
||||
# @snapshot-delete: snapshot delete job type, see "snapshot-delete" (since 6.0)
|
||||
#
|
||||
# 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:
|
||||
|
@ -1854,3 +1854,176 @@
|
||||
# Since: 5.2
|
||||
##
|
||||
{ 'command': 'query-dirty-rate', 'returns': 'DirtyRateInfo' }
|
||||
|
||||
##
|
||||
# @snapshot-save:
|
||||
#
|
||||
# Save a VM snapshot
|
||||
#
|
||||
# @job-id: identifier for the newly created job
|
||||
# @tag: name of the snapshot to create
|
||||
# @vmstate: block device node name to save vmstate to
|
||||
# @devices: list of block device node names to save a snapshot to
|
||||
#
|
||||
# Applications should not assume that the snapshot save is complete
|
||||
# when this command returns. The job commands / events must be used
|
||||
# to determine completion and to fetch details of any errors that arise.
|
||||
#
|
||||
# Note that execution of the guest CPUs may be stopped during the
|
||||
# time it takes to save the snapshot. A future version of QEMU
|
||||
# may ensure CPUs are executing continuously.
|
||||
#
|
||||
# It is strongly recommended that @devices contain all writable
|
||||
# block device nodes if a consistent snapshot is required.
|
||||
#
|
||||
# If @tag already exists, an error will be reported
|
||||
#
|
||||
# Returns: nothing
|
||||
#
|
||||
# Example:
|
||||
#
|
||||
# -> { "execute": "snapshot-save",
|
||||
# "data": {
|
||||
# "job-id": "snapsave0",
|
||||
# "tag": "my-snap",
|
||||
# "vmstate": "disk0",
|
||||
# "devices": ["disk0", "disk1"]
|
||||
# }
|
||||
# }
|
||||
# <- { "return": { } }
|
||||
# <- {"event": "JOB_STATUS_CHANGE",
|
||||
# "data": {"status": "created", "id": "snapsave0"}}
|
||||
# <- {"event": "JOB_STATUS_CHANGE",
|
||||
# "data": {"status": "running", "id": "snapsave0"}}
|
||||
# <- {"event": "STOP"}
|
||||
# <- {"event": "RESUME"}
|
||||
# <- {"event": "JOB_STATUS_CHANGE",
|
||||
# "data": {"status": "waiting", "id": "snapsave0"}}
|
||||
# <- {"event": "JOB_STATUS_CHANGE",
|
||||
# "data": {"status": "pending", "id": "snapsave0"}}
|
||||
# <- {"event": "JOB_STATUS_CHANGE",
|
||||
# "data": {"status": "concluded", "id": "snapsave0"}}
|
||||
# -> {"execute": "query-jobs"}
|
||||
# <- {"return": [{"current-progress": 1,
|
||||
# "status": "concluded",
|
||||
# "total-progress": 1,
|
||||
# "type": "snapshot-save",
|
||||
# "id": "snapsave0"}]}
|
||||
#
|
||||
# Since: 6.0
|
||||
##
|
||||
{ '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.
|
||||
# @vmstate: block device node name to load vmstate from
|
||||
# @devices: list of block device node names to load a snapshot from
|
||||
#
|
||||
# Applications should not assume that the snapshot load is complete
|
||||
# when this command returns. The job commands / events must be used
|
||||
# to determine completion and to fetch details of any errors that arise.
|
||||
#
|
||||
# Note that execution of the guest CPUs will be stopped during the
|
||||
# time it takes to load the snapshot.
|
||||
#
|
||||
# It is strongly recommended that @devices contain all writable
|
||||
# block device nodes that can have changed since the original
|
||||
# @snapshot-save command execution.
|
||||
#
|
||||
# Returns: nothing
|
||||
#
|
||||
# Example:
|
||||
#
|
||||
# -> { "execute": "snapshot-load",
|
||||
# "data": {
|
||||
# "job-id": "snapload0",
|
||||
# "tag": "my-snap",
|
||||
# "vmstate": "disk0",
|
||||
# "devices": ["disk0", "disk1"]
|
||||
# }
|
||||
# }
|
||||
# <- { "return": { } }
|
||||
# <- {"event": "JOB_STATUS_CHANGE",
|
||||
# "data": {"status": "created", "id": "snapload0"}}
|
||||
# <- {"event": "JOB_STATUS_CHANGE",
|
||||
# "data": {"status": "running", "id": "snapload0"}}
|
||||
# <- {"event": "STOP"}
|
||||
# <- {"event": "RESUME"}
|
||||
# <- {"event": "JOB_STATUS_CHANGE",
|
||||
# "data": {"status": "waiting", "id": "snapload0"}}
|
||||
# <- {"event": "JOB_STATUS_CHANGE",
|
||||
# "data": {"status": "pending", "id": "snapload0"}}
|
||||
# <- {"event": "JOB_STATUS_CHANGE",
|
||||
# "data": {"status": "concluded", "id": "snapload0"}}
|
||||
# -> {"execute": "query-jobs"}
|
||||
# <- {"return": [{"current-progress": 1,
|
||||
# "status": "concluded",
|
||||
# "total-progress": 1,
|
||||
# "type": "snapshot-load",
|
||||
# "id": "snapload0"}]}
|
||||
#
|
||||
# Since: 6.0
|
||||
##
|
||||
{ '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 delete is complete
|
||||
# when this command returns. The job commands / events must be used
|
||||
# to determine completion and to fetch details of any errors that arise.
|
||||
#
|
||||
# Returns: nothing
|
||||
#
|
||||
# Example:
|
||||
#
|
||||
# -> { "execute": "snapshot-delete",
|
||||
# "data": {
|
||||
# "job-id": "snapdelete0",
|
||||
# "tag": "my-snap",
|
||||
# "devices": ["disk0", "disk1"]
|
||||
# }
|
||||
# }
|
||||
# <- { "return": { } }
|
||||
# <- {"event": "JOB_STATUS_CHANGE",
|
||||
# "data": {"status": "created", "id": "snapdelete0"}}
|
||||
# <- {"event": "JOB_STATUS_CHANGE",
|
||||
# "data": {"status": "running", "id": "snapdelete0"}}
|
||||
# <- {"event": "JOB_STATUS_CHANGE",
|
||||
# "data": {"status": "waiting", "id": "snapdelete0"}}
|
||||
# <- {"event": "JOB_STATUS_CHANGE",
|
||||
# "data": {"status": "pending", "id": "snapdelete0"}}
|
||||
# <- {"event": "JOB_STATUS_CHANGE",
|
||||
# "data": {"status": "concluded", "id": "snapdelete0"}}
|
||||
# -> {"execute": "query-jobs"}
|
||||
# <- {"return": [{"current-progress": 1,
|
||||
# "status": "concluded",
|
||||
# "total-progress": 1,
|
||||
# "type": "snapshot-delete",
|
||||
# "id": "snapdelete0"}]}
|
||||
#
|
||||
# Since: 6.0
|
||||
##
|
||||
{ 'command': 'snapshot-delete',
|
||||
'data': { 'job-id': 'str',
|
||||
'tag': 'str',
|
||||
'devices': ['str'] } }
|
||||
|
Loading…
Reference in New Issue
Block a user