libvirt supports taking external disk snapshots on a running VM, using qemu's "blockdev-snapshot" command. qemu also supports "blockdev-snapshot-internal-sync" to do the same for internal snapshots. This commit wraps this (old) qemu capability to allow libvirt users to take internal disk snapshots on a running VM. This will only work for disk types which support internal snapshots, and thus we require the disk type to be part of a white list of known types (only RBD disks for start). Signed-off-by: Or Ozeri <oro@xxxxxxxxxx> --- src/qemu/qemu_monitor.c | 9 ++ src/qemu/qemu_monitor.h | 5 ++ src/qemu/qemu_monitor_json.c | 14 ++++ src/qemu/qemu_monitor_json.h | 5 ++ src/qemu/qemu_snapshot.c | 83 +++++++++++-------- .../disk_snapshot.xml | 2 +- .../disk_snapshot.xml | 2 +- .../disk_snapshot_redefine.xml | 2 +- tests/qemumonitorjsontest.c | 1 + 9 files changed, 84 insertions(+), 39 deletions(-) diff --git a/src/qemu/qemu_monitor.c b/src/qemu/qemu_monitor.c index 38f89167e0..f6dab34243 100644 --- a/src/qemu/qemu_monitor.c +++ b/src/qemu/qemu_monitor.c @@ -4225,6 +4225,15 @@ qemuMonitorTransactionSnapshotBlockdev(virJSONValue *actions, } +int +qemuMonitorTransactionInternalSnapshotBlockdev(virJSONValue *actions, + const char *device, + const char *name) +{ + return qemuMonitorJSONTransactionInternalSnapshotBlockdev(actions, device, name); +} + + int qemuMonitorTransactionBackup(virJSONValue *actions, const char *device, diff --git a/src/qemu/qemu_monitor.h b/src/qemu/qemu_monitor.h index 2d16214ba2..1bfd1ccbc2 100644 --- a/src/qemu/qemu_monitor.h +++ b/src/qemu/qemu_monitor.h @@ -1411,6 +1411,11 @@ qemuMonitorTransactionSnapshotBlockdev(virJSONValue *actions, const char *node, const char *overlay); +int +qemuMonitorTransactionInternalSnapshotBlockdev(virJSONValue *actions, + const char *device, + const char *name); + typedef enum { QEMU_MONITOR_TRANSACTION_BACKUP_SYNC_MODE_NONE = 0, QEMU_MONITOR_TRANSACTION_BACKUP_SYNC_MODE_INCREMENTAL, diff --git a/src/qemu/qemu_monitor_json.c b/src/qemu/qemu_monitor_json.c index db99017555..002a6caa52 100644 --- a/src/qemu/qemu_monitor_json.c +++ b/src/qemu/qemu_monitor_json.c @@ -8307,6 +8307,20 @@ qemuMonitorJSONTransactionSnapshotBlockdev(virJSONValue *actions, NULL); } + +int +qemuMonitorJSONTransactionInternalSnapshotBlockdev(virJSONValue *actions, + const char *device, + const char *name) +{ + return qemuMonitorJSONTransactionAdd(actions, + "blockdev-snapshot-internal-sync", + "s:device", device, + "s:name", name, + NULL); +} + + VIR_ENUM_DECL(qemuMonitorTransactionBackupSyncMode); VIR_ENUM_IMPL(qemuMonitorTransactionBackupSyncMode, QEMU_MONITOR_TRANSACTION_BACKUP_SYNC_MODE_LAST, diff --git a/src/qemu/qemu_monitor_json.h b/src/qemu/qemu_monitor_json.h index 6f376cf9b7..313004f327 100644 --- a/src/qemu/qemu_monitor_json.h +++ b/src/qemu/qemu_monitor_json.h @@ -779,6 +779,11 @@ qemuMonitorJSONTransactionSnapshotBlockdev(virJSONValue *actions, const char *node, const char *overlay); +int +qemuMonitorJSONTransactionInternalSnapshotBlockdev(virJSONValue *actions, + const char *device, + const char *name); + int qemuMonitorJSONTransactionBackup(virJSONValue *actions, const char *device, diff --git a/src/qemu/qemu_snapshot.c b/src/qemu/qemu_snapshot.c index b8416808b3..9146ecae2f 100644 --- a/src/qemu/qemu_snapshot.c +++ b/src/qemu/qemu_snapshot.c @@ -639,20 +639,13 @@ qemuSnapshotPrepare(virDomainObj *vm, case VIR_DOMAIN_SNAPSHOT_LOCATION_INTERNAL: found_internal = true; - if (def->state == VIR_DOMAIN_SNAPSHOT_DISK_SNAPSHOT && active) { - virReportError(VIR_ERR_CONFIG_UNSUPPORTED, - _("active qemu domains require external disk " - "snapshots; disk %s requested internal"), - disk->name); - return -1; - } - if (qemuSnapshotPrepareDiskInternal(dom_disk, active) < 0) return -1; if (dom_disk->src->format > 0 && - dom_disk->src->format != VIR_STORAGE_FILE_QCOW2) { + dom_disk->src->format != VIR_STORAGE_FILE_QCOW2 && + dom_disk->src->protocol != VIR_STORAGE_NET_PROTOCOL_RBD) { virReportError(VIR_ERR_CONFIG_UNSUPPORTED, _("internal snapshot for disk %s unsupported " "for storage type %s"), @@ -727,7 +720,7 @@ qemuSnapshotPrepare(virDomainObj *vm, } /* disk snapshot requires at least one disk */ - if (def->state == VIR_DOMAIN_SNAPSHOT_DISK_SNAPSHOT && !external) { + if (def->state == VIR_DOMAIN_SNAPSHOT_DISK_SNAPSHOT && !external && !found_internal) { virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s", _("disk-only snapshots require at least " "one disk to be selected for snapshot")); @@ -843,6 +836,7 @@ qemuSnapshotDiskCleanup(qemuSnapshotDiskData *data, struct _qemuSnapshotDiskContext { qemuSnapshotDiskData *dd; size_t ndd; + bool has_internal; virJSONValue *actions; @@ -1061,17 +1055,17 @@ qemuSnapshotDiskPrepareOne(qemuSnapshotDiskContext *snapctxt, /** - * qemuSnapshotDiskPrepareActiveExternal: + * qemuSnapshotDiskPrepareActive: * * Collects and prepares a list of structures that hold information about disks * that are selected for the snapshot. */ static qemuSnapshotDiskContext * -qemuSnapshotDiskPrepareActiveExternal(virDomainObj *vm, - virDomainMomentObj *snap, - bool reuse, - GHashTable *blockNamedNodeData, - virDomainAsyncJob asyncJob) +qemuSnapshotDiskPrepareActive(virDomainObj *vm, + virDomainMomentObj *snap, + bool reuse, + GHashTable *blockNamedNodeData, + virDomainAsyncJob asyncJob) { g_autoptr(qemuSnapshotDiskContext) snapctxt = NULL; size_t i; @@ -1080,16 +1074,33 @@ qemuSnapshotDiskPrepareActiveExternal(virDomainObj *vm, snapctxt = qemuSnapshotDiskContextNew(snapdef->ndisks, vm, asyncJob); for (i = 0; i < snapdef->ndisks; i++) { - if (snapdef->disks[i].snapshot != VIR_DOMAIN_SNAPSHOT_LOCATION_EXTERNAL) - continue; + switch (snapdef->disks[i].snapshot) { + case VIR_DOMAIN_SNAPSHOT_LOCATION_EXTERNAL: { + if (qemuSnapshotDiskPrepareOne(snapctxt, + vm->def->disks[i], + snapdef->disks + i, + blockNamedNodeData, + reuse, + true) < 0) + return NULL; + break; + } - if (qemuSnapshotDiskPrepareOne(snapctxt, - vm->def->disks[i], - snapdef->disks + i, - blockNamedNodeData, - reuse, - true) < 0) - return NULL; + case VIR_DOMAIN_SNAPSHOT_LOCATION_INTERNAL: { + snapctxt->has_internal = true; + if (qemuMonitorTransactionInternalSnapshotBlockdev(snapctxt->actions, + vm->def->disks[i]->src->nodeformat, + snapdef->disks[i].snapshot_name) < 0) + return NULL; + break; + } + + case VIR_DOMAIN_SNAPSHOT_LOCATION_DEFAULT: + case VIR_DOMAIN_SNAPSHOT_LOCATION_NO: + case VIR_DOMAIN_SNAPSHOT_LOCATION_MANUAL: + case VIR_DOMAIN_SNAPSHOT_LOCATION_LAST: + continue; + } } return g_steal_pointer(&snapctxt); @@ -1173,7 +1184,7 @@ qemuSnapshotDiskCreate(qemuSnapshotDiskContext *snapctxt) int rc; /* check whether there's anything to do */ - if (snapctxt->ndd == 0) + if (snapctxt->ndd == 0 && !snapctxt->has_internal) return 0; if (qemuDomainObjEnterMonitorAsync(snapctxt->vm, snapctxt->asyncJob) < 0) @@ -1206,11 +1217,11 @@ qemuSnapshotDiskCreate(qemuSnapshotDiskContext *snapctxt) /* The domain is expected to be locked and active. */ static int -qemuSnapshotCreateActiveExternalDisks(virDomainObj *vm, - virDomainMomentObj *snap, - GHashTable *blockNamedNodeData, - unsigned int flags, - virDomainAsyncJob asyncJob) +qemuSnapshotCreateActiveDisks(virDomainObj *vm, + virDomainMomentObj *snap, + GHashTable *blockNamedNodeData, + unsigned int flags, + virDomainAsyncJob asyncJob) { bool reuse = (flags & VIR_DOMAIN_SNAPSHOT_CREATE_REUSE_EXT) != 0; g_autoptr(qemuSnapshotDiskContext) snapctxt = NULL; @@ -1220,8 +1231,8 @@ qemuSnapshotCreateActiveExternalDisks(virDomainObj *vm, /* prepare a list of objects to use in the vm definition so that we don't * have to roll back later */ - if (!(snapctxt = qemuSnapshotDiskPrepareActiveExternal(vm, snap, reuse, - blockNamedNodeData, asyncJob))) + if (!(snapctxt = qemuSnapshotDiskPrepareActive(vm, snap, reuse, + blockNamedNodeData, asyncJob))) return -1; if (qemuSnapshotDiskCreate(snapctxt) < 0) @@ -1361,9 +1372,9 @@ qemuSnapshotCreateActiveExternal(virQEMUDriver *driver, /* the domain is now paused if a memory snapshot was requested */ - if ((ret = qemuSnapshotCreateActiveExternalDisks(vm, snap, - blockNamedNodeData, flags, - VIR_ASYNC_JOB_SNAPSHOT)) < 0) + if ((ret = qemuSnapshotCreateActiveDisks(vm, snap, + blockNamedNodeData, flags, + VIR_ASYNC_JOB_SNAPSHOT)) < 0) goto cleanup; /* the snapshot is complete now */ diff --git a/tests/qemudomainsnapshotxml2xmlin/disk_snapshot.xml b/tests/qemudomainsnapshotxml2xmlin/disk_snapshot.xml index cf5ea0814e..87b6251a7f 100644 --- a/tests/qemudomainsnapshotxml2xmlin/disk_snapshot.xml +++ b/tests/qemudomainsnapshotxml2xmlin/disk_snapshot.xml @@ -4,7 +4,7 @@ <disks> <disk name='/dev/HostVG/QEMUGuest1'/> <disk name='hdb' snapshot='no'/> - <disk name='hdc' snapshot='internal'/> + <disk name='hdc' snapshot='internal' snapshotName='snap1'/> <disk name='hdd' snapshot='external'> <source/> <driver type='qed'/> diff --git a/tests/qemudomainsnapshotxml2xmlout/disk_snapshot.xml b/tests/qemudomainsnapshotxml2xmlout/disk_snapshot.xml index 76c543d25c..6cf93183d5 100644 --- a/tests/qemudomainsnapshotxml2xmlout/disk_snapshot.xml +++ b/tests/qemudomainsnapshotxml2xmlout/disk_snapshot.xml @@ -5,7 +5,7 @@ <disks> <disk name='/dev/HostVG/QEMUGuest1'/> <disk name='hdb' snapshot='no'/> - <disk name='hdc' snapshot='internal'/> + <disk name='hdc' snapshot='internal' snapshotName='snap1'/> <disk name='hdd' snapshot='external' type='file'> <driver type='qed'/> </disk> diff --git a/tests/qemudomainsnapshotxml2xmlout/disk_snapshot_redefine.xml b/tests/qemudomainsnapshotxml2xmlout/disk_snapshot_redefine.xml index 24b41ba7c5..f574793edf 100644 --- a/tests/qemudomainsnapshotxml2xmlout/disk_snapshot_redefine.xml +++ b/tests/qemudomainsnapshotxml2xmlout/disk_snapshot_redefine.xml @@ -10,7 +10,7 @@ <disks> <disk name='hda' snapshot='no'/> <disk name='hdb' snapshot='no'/> - <disk name='hdc' snapshot='internal'/> + <disk name='hdc' snapshot='internal' snapshotName='snap1'/> <disk name='hdd' snapshot='external' type='file'> <driver type='qed'/> <source file='/path/to/generated4'/> diff --git a/tests/qemumonitorjsontest.c b/tests/qemumonitorjsontest.c index 1db1f2b949..1269c74e43 100644 --- a/tests/qemumonitorjsontest.c +++ b/tests/qemumonitorjsontest.c @@ -2587,6 +2587,7 @@ testQemuMonitorJSONTransaction(const void *opaque) qemuMonitorTransactionBitmapDisable(actions, "node4", "bitmap4") < 0 || qemuMonitorTransactionBitmapMerge(actions, "node5", "bitmap5", &mergebitmaps) < 0 || qemuMonitorTransactionSnapshotBlockdev(actions, "node7", "overlay7") < 0 || + qemuMonitorTransactionInternalSnapshotBlockdev(actions, "device1", "snapshot1") < 0 || qemuMonitorTransactionBackup(actions, "dev8", "job8", "target8", "bitmap8", QEMU_MONITOR_TRANSACTION_BACKUP_SYNC_MODE_NONE) < 0 || qemuMonitorTransactionBackup(actions, "dev9", "job9", "target9", "bitmap9", -- 2.25.1