:p
atchew
Login
This fixes reverting external snapshots to not error out in cases where it should work and makes it correctly load the memory state when reverting to snapshot of running VM. Pavel Hrdina (7): qemu_saveimage: extract starting process to qemuSaveImageStartProcess qemuSaveImageStartProcess: allow setting reason for audit log qemuSaveImageStartProcess: add snapshot argument qemuSaveImageStartProcess: make it possible to use without header qemu_snapshot: fix reverting external snapshot when not all disks are included qemu_snapshot: correctly load the saved memory state file NEWS: document support for reverting external snapshots NEWS.rst | 8 +++ src/qemu/qemu_saveimage.c | 111 ++++++++++++++++++++++++++------------ src/qemu/qemu_saveimage.h | 14 +++++ src/qemu/qemu_snapshot.c | 90 ++++++++++++++++++++----------- 4 files changed, 158 insertions(+), 65 deletions(-) -- 2.41.0
Part of qemuSaveImageStartVM() function will be used when reverting external snapshots. To avoid duplicating code and logic extract the shared bits into separate function. Signed-off-by: Pavel Hrdina <phrdina@redhat.com> --- src/qemu/qemu_saveimage.c | 103 ++++++++++++++++++++++++++------------ src/qemu/qemu_saveimage.h | 12 +++++ 2 files changed, 83 insertions(+), 32 deletions(-) diff --git a/src/qemu/qemu_saveimage.c b/src/qemu/qemu_saveimage.c index XXXXXXX..XXXXXXX 100644 --- a/src/qemu/qemu_saveimage.c +++ b/src/qemu/qemu_saveimage.c @@ -XXX,XX +XXX,XX @@ qemuSaveImageOpen(virQEMUDriver *driver, return ret; } +/** + * qemuSaveImageStartProcess: + * @conn: connection object + * @driver: qemu driver object + * @vm: domain object + * @fd: FD pointer of memory state file + * @path: path to memory state file + * @header: header from memory state file + * @cookie: cookie from memory state file + * @asyncJob: type of asynchronous job + * @start_flags: flags to start QEMU process with + * @started: boolean to store if QEMU process was started + * + * Start VM with existing memory state. Make sure that the stored memory state + * is correctly decompressed so it can be loaded by QEMU process. + * + * Returns 0 on success, -1 on error. + */ int -qemuSaveImageStartVM(virConnectPtr conn, - virQEMUDriver *driver, - virDomainObj *vm, - int *fd, - virQEMUSaveData *data, - const char *path, - bool start_paused, - bool reset_nvram, - virDomainAsyncJob asyncJob) +qemuSaveImageStartProcess(virConnectPtr conn, + virQEMUDriver *driver, + virDomainObj *vm, + int *fd, + const char *path, + virQEMUSaveHeader *header, + qemuDomainSaveCookie *cookie, + virDomainAsyncJob asyncJob, + unsigned int start_flags, + bool *started) { qemuDomainObjPrivate *priv = vm->privateData; - int ret = -1; - bool started = false; - virObjectEvent *event; VIR_AUTOCLOSE intermediatefd = -1; g_autoptr(virCommand) cmd = NULL; g_autofree char *errbuf = NULL; - g_autoptr(virQEMUDriverConfig) cfg = virQEMUDriverGetConfig(driver); - virQEMUSaveHeader *header = &data->header; - g_autoptr(qemuDomainSaveCookie) cookie = NULL; int rc = 0; - unsigned int start_flags = VIR_QEMU_PROCESS_START_PAUSED | - VIR_QEMU_PROCESS_START_GEN_VMID; - - if (reset_nvram) - start_flags |= VIR_QEMU_PROCESS_START_RESET_NVRAM; - - if (virSaveCookieParseString(data->cookie, (virObject **)&cookie, - virDomainXMLOptionGetSaveCookie(driver->xmlopt)) < 0) - goto cleanup; if ((header->version == 2) && (header->compressed != QEMU_SAVE_FORMAT_RAW)) { if (!(cmd = qemuSaveImageGetCompressionCommand(header->compressed))) - goto cleanup; + return -1; intermediatefd = *fd; *fd = -1; @@ -XXX,XX +XXX,XX @@ qemuSaveImageStartVM(virConnectPtr conn, if (virCommandRunAsync(cmd, NULL) < 0) { *fd = intermediatefd; intermediatefd = -1; - goto cleanup; + return -1; } } @@ -XXX,XX +XXX,XX @@ qemuSaveImageStartVM(virConnectPtr conn, */ if (cookie && qemuDomainFixupCPUs(vm, &cookie->cpu) < 0) - goto cleanup; + return -1; if (cookie && !cookie->slirpHelper) priv->disableSlirp = true; @@ -XXX,XX +XXX,XX @@ qemuSaveImageStartVM(virConnectPtr conn, asyncJob, "stdio", *fd, path, NULL, VIR_NETDEV_VPORT_PROFILE_OP_RESTORE, start_flags) == 0) - started = true; + *started = true; if (intermediatefd != -1) { virErrorPtr orig_err = NULL; - if (!started) { + if (!*started) { /* if there was an error setting up qemu, the intermediate * process will wait forever to write to stdout, so we * must manually kill it and ignore any error related to @@ -XXX,XX +XXX,XX @@ qemuSaveImageStartVM(virConnectPtr conn, rc = -1; } - virDomainAuditStart(vm, "restored", started); - if (!started || rc < 0) - goto cleanup; + virDomainAuditStart(vm, "restored", *started); + if (!*started || rc < 0) + return -1; /* qemuProcessStart doesn't unset the qemu error reporting infrastructure * in case of migration (which is used in this case) so we need to reset it * so that the handle to virtlogd is not held open unnecessarily */ qemuMonitorSetDomainLog(qemuDomainGetMonitor(vm), NULL, NULL, NULL); + return 0; +} + +int +qemuSaveImageStartVM(virConnectPtr conn, + virQEMUDriver *driver, + virDomainObj *vm, + int *fd, + virQEMUSaveData *data, + const char *path, + bool start_paused, + bool reset_nvram, + virDomainAsyncJob asyncJob) +{ + int ret = -1; + bool started = false; + virObjectEvent *event; + g_autoptr(virQEMUDriverConfig) cfg = virQEMUDriverGetConfig(driver); + virQEMUSaveHeader *header = &data->header; + g_autoptr(qemuDomainSaveCookie) cookie = NULL; + unsigned int start_flags = VIR_QEMU_PROCESS_START_PAUSED | + VIR_QEMU_PROCESS_START_GEN_VMID; + + if (reset_nvram) + start_flags |= VIR_QEMU_PROCESS_START_RESET_NVRAM; + + if (virSaveCookieParseString(data->cookie, (virObject **)&cookie, + virDomainXMLOptionGetSaveCookie(driver->xmlopt)) < 0) + goto cleanup; + + if (qemuSaveImageStartProcess(conn, driver, vm, fd, path, header, cookie, + asyncJob, start_flags, &started) < 0) { + goto cleanup; + } + event = virDomainEventLifecycleNewFromObj(vm, VIR_DOMAIN_EVENT_STARTED, VIR_DOMAIN_EVENT_STARTED_RESTORED); diff --git a/src/qemu/qemu_saveimage.h b/src/qemu/qemu_saveimage.h index XXXXXXX..XXXXXXX 100644 --- a/src/qemu/qemu_saveimage.h +++ b/src/qemu/qemu_saveimage.h @@ -XXX,XX +XXX,XX @@ qemuSaveImageUpdateDef(virQEMUDriver *driver, virDomainDef *def, const char *newxml); +int +qemuSaveImageStartProcess(virConnectPtr conn, + virQEMUDriver *driver, + virDomainObj *vm, + int *fd, + const char *path, + virQEMUSaveHeader *header, + qemuDomainSaveCookie *cookie, + virDomainAsyncJob asyncJob, + unsigned int start_flags, + bool *started); + int qemuSaveImageStartVM(virConnectPtr conn, virQEMUDriver *driver, -- 2.41.0
When called by snapshot code we will need to use different reason. Signed-off-by: Pavel Hrdina <phrdina@redhat.com> --- src/qemu/qemu_saveimage.c | 6 ++++-- src/qemu/qemu_saveimage.h | 1 + 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/qemu/qemu_saveimage.c b/src/qemu/qemu_saveimage.c index XXXXXXX..XXXXXXX 100644 --- a/src/qemu/qemu_saveimage.c +++ b/src/qemu/qemu_saveimage.c @@ -XXX,XX +XXX,XX @@ qemuSaveImageOpen(virQEMUDriver *driver, * @cookie: cookie from memory state file * @asyncJob: type of asynchronous job * @start_flags: flags to start QEMU process with + * @reason: audit log reason * @started: boolean to store if QEMU process was started * * Start VM with existing memory state. Make sure that the stored memory state @@ -XXX,XX +XXX,XX @@ qemuSaveImageStartProcess(virConnectPtr conn, qemuDomainSaveCookie *cookie, virDomainAsyncJob asyncJob, unsigned int start_flags, + const char *reason, bool *started) { qemuDomainObjPrivate *priv = vm->privateData; @@ -XXX,XX +XXX,XX @@ qemuSaveImageStartProcess(virConnectPtr conn, rc = -1; } - virDomainAuditStart(vm, "restored", *started); + virDomainAuditStart(vm, reason, started); if (!*started || rc < 0) return -1; @@ -XXX,XX +XXX,XX @@ qemuSaveImageStartVM(virConnectPtr conn, goto cleanup; if (qemuSaveImageStartProcess(conn, driver, vm, fd, path, header, cookie, - asyncJob, start_flags, &started) < 0) { + asyncJob, start_flags, "restored", &started) < 0) { goto cleanup; } diff --git a/src/qemu/qemu_saveimage.h b/src/qemu/qemu_saveimage.h index XXXXXXX..XXXXXXX 100644 --- a/src/qemu/qemu_saveimage.h +++ b/src/qemu/qemu_saveimage.h @@ -XXX,XX +XXX,XX @@ qemuSaveImageStartProcess(virConnectPtr conn, qemuDomainSaveCookie *cookie, virDomainAsyncJob asyncJob, unsigned int start_flags, + const char *reason, bool *started); int -- 2.41.0
When called from snapshot code we will need to pass snapshot object in order to make internal snapshots work correctly. Signed-off-by: Pavel Hrdina <phrdina@redhat.com> --- src/qemu/qemu_saveimage.c | 6 ++++-- src/qemu/qemu_saveimage.h | 1 + 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/qemu/qemu_saveimage.c b/src/qemu/qemu_saveimage.c index XXXXXXX..XXXXXXX 100644 --- a/src/qemu/qemu_saveimage.c +++ b/src/qemu/qemu_saveimage.c @@ -XXX,XX +XXX,XX @@ qemuSaveImageOpen(virQEMUDriver *driver, * @vm: domain object * @fd: FD pointer of memory state file * @path: path to memory state file + * @snapshot: snapshot to load when starting QEMU process or NULL * @header: header from memory state file * @cookie: cookie from memory state file * @asyncJob: type of asynchronous job @@ -XXX,XX +XXX,XX @@ qemuSaveImageStartProcess(virConnectPtr conn, virDomainObj *vm, int *fd, const char *path, + virDomainMomentObj *snapshot, virQEMUSaveHeader *header, qemuDomainSaveCookie *cookie, virDomainAsyncJob asyncJob, @@ -XXX,XX +XXX,XX @@ qemuSaveImageStartProcess(virConnectPtr conn, priv->disableSlirp = true; if (qemuProcessStart(conn, driver, vm, cookie ? cookie->cpu : NULL, - asyncJob, "stdio", *fd, path, NULL, + asyncJob, "stdio", *fd, path, snapshot, VIR_NETDEV_VPORT_PROFILE_OP_RESTORE, start_flags) == 0) *started = true; @@ -XXX,XX +XXX,XX @@ qemuSaveImageStartVM(virConnectPtr conn, virDomainXMLOptionGetSaveCookie(driver->xmlopt)) < 0) goto cleanup; - if (qemuSaveImageStartProcess(conn, driver, vm, fd, path, header, cookie, + if (qemuSaveImageStartProcess(conn, driver, vm, fd, path, NULL, header, cookie, asyncJob, start_flags, "restored", &started) < 0) { goto cleanup; } diff --git a/src/qemu/qemu_saveimage.h b/src/qemu/qemu_saveimage.h index XXXXXXX..XXXXXXX 100644 --- a/src/qemu/qemu_saveimage.h +++ b/src/qemu/qemu_saveimage.h @@ -XXX,XX +XXX,XX @@ qemuSaveImageStartProcess(virConnectPtr conn, virDomainObj *vm, int *fd, const char *path, + virDomainMomentObj *snapshot, virQEMUSaveHeader *header, qemuDomainSaveCookie *cookie, virDomainAsyncJob asyncJob, -- 2.41.0
When used with internal snapshots there is no header to be used and no memory state to be decompressed. Signed-off-by: Pavel Hrdina <phrdina@redhat.com> --- src/qemu/qemu_saveimage.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/qemu/qemu_saveimage.c b/src/qemu/qemu_saveimage.c index XXXXXXX..XXXXXXX 100644 --- a/src/qemu/qemu_saveimage.c +++ b/src/qemu/qemu_saveimage.c @@ -XXX,XX +XXX,XX @@ qemuSaveImageOpen(virQEMUDriver *driver, * @fd: FD pointer of memory state file * @path: path to memory state file * @snapshot: snapshot to load when starting QEMU process or NULL - * @header: header from memory state file + * @header: header from memory state file or NULL * @cookie: cookie from memory state file * @asyncJob: type of asynchronous job * @start_flags: flags to start QEMU process with @@ -XXX,XX +XXX,XX @@ qemuSaveImageStartProcess(virConnectPtr conn, g_autofree char *errbuf = NULL; int rc = 0; - if ((header->version == 2) && + if (header && (header->version == 2) && (header->compressed != QEMU_SAVE_FORMAT_RAW)) { if (!(cmd = qemuSaveImageGetCompressionCommand(header->compressed))) return -1; -- 2.41.0
We need to skip all disks that have snapshot type other than 'external'. Signed-off-by: Pavel Hrdina <phrdina@redhat.com> --- src/qemu/qemu_snapshot.c | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/qemu/qemu_snapshot.c b/src/qemu/qemu_snapshot.c index XXXXXXX..XXXXXXX 100644 --- a/src/qemu/qemu_snapshot.c +++ b/src/qemu/qemu_snapshot.c @@ -XXX,XX +XXX,XX @@ qemuSnapshotRevertExternalPrepare(virDomainObj *vm, virDomainSnapshotDiskDef *snapdisk = &tmpsnapdef->disks[i]; virDomainDiskDef *domdisk = domdef->disks[i]; + if (snapdisk->snapshot != VIR_DOMAIN_SNAPSHOT_LOCATION_EXTERNAL) + continue; + if (qemuSnapshotPrepareDiskExternal(domdisk, snapdisk, active, false) < 0) return -1; } @@ -XXX,XX +XXX,XX @@ qemuSnapshotRevertExternalActive(virDomainObj *vm, return -1; for (i = 0; i < tmpsnapdef->ndisks; i++) { + if (tmpsnapdef->disks[i].snapshot != VIR_DOMAIN_SNAPSHOT_LOCATION_EXTERNAL) + continue; + if (qemuSnapshotDiskPrepareOne(snapctxt, vm->def->disks[i], tmpsnapdef->disks + i, @@ -XXX,XX +XXX,XX @@ qemuSnapshotRevertExternalFinish(virDomainObj *vm, for (i = 0; i < curdef->nrevertdisks; i++) { virDomainSnapshotDiskDef *snapdisk = &(curdef->revertdisks[i]); + if (snapdisk->snapshot != VIR_DOMAIN_SNAPSHOT_LOCATION_EXTERNAL) + continue; + if (virStorageSourceInit(snapdisk->src) < 0 || virStorageSourceUnlink(snapdisk->src) < 0) { VIR_WARN("Failed to remove snapshot image '%s'", @@ -XXX,XX +XXX,XX @@ qemuSnapshotRevertExternalFinish(virDomainObj *vm, for (i = 0; i < curdef->ndisks; i++) { virDomainSnapshotDiskDef *snapdisk = &(curdef->disks[i]); + if (snapdisk->snapshot != VIR_DOMAIN_SNAPSHOT_LOCATION_EXTERNAL) + continue; + if (virStorageSourceInit(snapdisk->src) < 0 || virStorageSourceUnlink(snapdisk->src) < 0) { VIR_WARN("Failed to remove snapshot image '%s'", -- 2.41.0
Original code assumed that the memory state file is only migration stream but it has additional metadata stored by libvirt. To correctly load the memory state file we need to reuse code that is used when restoring domain from saved image. Signed-off-by: Pavel Hrdina <phrdina@redhat.com> --- src/qemu/qemu_snapshot.c | 78 ++++++++++++++++++++++++---------------- 1 file changed, 47 insertions(+), 31 deletions(-) diff --git a/src/qemu/qemu_snapshot.c b/src/qemu/qemu_snapshot.c index XXXXXXX..XXXXXXX 100644 --- a/src/qemu/qemu_snapshot.c +++ b/src/qemu/qemu_snapshot.c @@ -XXX,XX +XXX,XX @@ qemuSnapshotRevertWriteMetadata(virDomainObj *vm, } +typedef struct _qemuSnapshotRevertMemoryData { + int fd; + char *path; + virQEMUSaveData *data; +} qemuSnapshotRevertMemoryData; + +static void +qemuSnapshotClearRevertMemoryData(qemuSnapshotRevertMemoryData *memdata) +{ + VIR_FORCE_CLOSE(memdata->fd); + virQEMUSaveDataFree(memdata->data); +} +G_DEFINE_AUTO_CLEANUP_CLEAR_FUNC(qemuSnapshotRevertMemoryData, qemuSnapshotClearRevertMemoryData); + + /** * qemuSnapshotRevertExternalPrepare: * @vm: domain object @@ -XXX,XX +XXX,XX @@ qemuSnapshotRevertWriteMetadata(virDomainObj *vm, * @snap: snapshot object we are reverting to * @config: live domain definition * @inactiveConfig: offline domain definition - * memsnapFD: pointer to store memory state file FD or NULL - * memsnapPath: pointer to store memory state file path or NULL + * @memdata: struct with data to load memory state * * Prepare new temporary snapshot definition @tmpsnapdef that will * be used while creating new overlay files after reverting to snapshot * @snap. In case we are reverting to snapshot with memory state it will - * open it and pass FD via @memsnapFD and path to the file via - * @memsnapPath, caller is responsible for freeing both @memsnapFD and - * memsnapPath. + * open it and store necessary data in @memdata. Caller is responsible + * to clear the data by using qemuSnapshotClearRevertMemoryData(). * * Returns 0 in success, -1 on error. */ @@ -XXX,XX +XXX,XX @@ qemuSnapshotRevertExternalPrepare(virDomainObj *vm, virDomainMomentObj *snap, virDomainDef *config, virDomainDef *inactiveConfig, - int *memsnapFD, - char **memsnapPath) + qemuSnapshotRevertMemoryData *memdata) { size_t i; bool active = virDomainObjIsActive(vm); @@ -XXX,XX +XXX,XX @@ qemuSnapshotRevertExternalPrepare(virDomainObj *vm, return -1; } - if (memsnapFD && memsnapPath && snapdef->memorysnapshotfile) { + if (memdata && snapdef->memorysnapshotfile) { virQEMUDriver *driver = ((qemuDomainObjPrivate *) vm->privateData)->driver; - g_autoptr(virQEMUDriverConfig) cfg = virQEMUDriverGetConfig(driver); + g_autoptr(virDomainDef) savedef = NULL; - *memsnapPath = snapdef->memorysnapshotfile; - *memsnapFD = qemuDomainOpenFile(cfg, NULL, *memsnapPath, O_RDONLY, NULL); + memdata->path = snapdef->memorysnapshotfile; + memdata->fd = qemuSaveImageOpen(driver, NULL, memdata->path, + &savedef, &memdata->data, + false, NULL, + false, false); + + if (memdata->fd < 0) + return -1; + + if (!virDomainDefCheckABIStability(savedef, domdef, driver->xmlopt)) + return -1; } return 0; @@ -XXX,XX +XXX,XX @@ qemuSnapshotRevertActive(virDomainObj *vm, virObjectEvent *event = NULL; virObjectEvent *event2 = NULL; virDomainMomentObj *loadSnap = NULL; - VIR_AUTOCLOSE memsnapFD = -1; - char *memsnapPath = NULL; int detail; bool defined = false; qemuDomainSaveCookie *cookie = (qemuDomainSaveCookie *) snapdef->cookie; int rc; g_autoptr(virDomainSnapshotDef) tmpsnapdef = NULL; + g_auto(qemuSnapshotRevertMemoryData) memdata = { -1, NULL, NULL }; + bool started = false; start_flags |= VIR_QEMU_PROCESS_START_PAUSED; @@ -XXX,XX +XXX,XX @@ qemuSnapshotRevertActive(virDomainObj *vm, if (qemuSnapshotRevertExternalPrepare(vm, tmpsnapdef, snap, *config, *inactiveConfig, - &memsnapFD, &memsnapPath) < 0) { + &memdata) < 0) { return -1; } } else { @@ -XXX,XX +XXX,XX @@ qemuSnapshotRevertActive(virDomainObj *vm, virDomainObjAssignDef(vm, config, true, NULL); - /* No cookie means libvirt which saved the domain was too old to - * mess up the CPU definitions. - */ - if (cookie && - qemuDomainFixupCPUs(vm, &cookie->cpu) < 0) + if (qemuSaveImageStartProcess(snapshot->domain->conn, driver, vm, + &memdata.fd, memdata.path, loadSnap, + memdata.data ? &memdata.data->header : NULL, + cookie, VIR_ASYNC_JOB_SNAPSHOT, start_flags, + "from-snapshot", &started) < 0) { + if (started) { + qemuProcessStop(driver, vm, VIR_DOMAIN_SHUTOFF_FAILED, + VIR_ASYNC_JOB_SNAPSHOT, + VIR_QEMU_PROCESS_STOP_MIGRATED); + } return -1; + } - rc = qemuProcessStart(snapshot->domain->conn, driver, vm, - cookie ? cookie->cpu : NULL, - VIR_ASYNC_JOB_SNAPSHOT, NULL, memsnapFD, - memsnapPath, loadSnap, - VIR_NETDEV_VPORT_PROFILE_OP_CREATE, - start_flags); - virDomainAuditStart(vm, "from-snapshot", rc >= 0); detail = VIR_DOMAIN_EVENT_STARTED_FROM_SNAPSHOT; event = virDomainEventLifecycleNewFromObj(vm, VIR_DOMAIN_EVENT_STARTED, detail); virObjectEventStateQueue(driver->domainEventState, event); - if (rc < 0) - return -1; - if (virDomainSnapshotIsExternal(snap)) { if (qemuSnapshotRevertExternalActive(vm, tmpsnapdef) < 0) @@ -XXX,XX +XXX,XX @@ qemuSnapshotRevertInactive(virDomainObj *vm, return -1; if (qemuSnapshotRevertExternalPrepare(vm, tmpsnapdef, snap, - NULL, *inactiveConfig, - NULL, NULL) < 0) { + NULL, *inactiveConfig, NULL) < 0) { return -1; } -- 2.41.0
Signed-off-by: Pavel Hrdina <phrdina@redhat.com> --- NEWS.rst | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/NEWS.rst b/NEWS.rst index XXXXXXX..XXXXXXX 100644 --- a/NEWS.rst +++ b/NEWS.rst @@ -XXX,XX +XXX,XX @@ v9.7.0 (unreleased) 2) pre-binding the variant driver using the ``--driver`` option of ``virsh nodedev-detach``. + * QEMU: implement reverting external snapshots + + Reverting external snapshots is now possible using the existing API + ``virDomainSnapshotRevert()``. Management application can check host + capabilities for ``<externalSnapshot/>`` element within the list of + guest features to see if the current libvirt supports both deleting + and reverting external snapshots. + * **Improvements** * **Bug fixes** -- 2.41.0
This fixes reverting external snapshots to not error out in cases where it should work and makes it correctly load the memory state when reverting to snapshot of running VM. This discards v2 completely and makes changes to v1: - moves qemuSaveImageStartProcess to qemu_process as qemuProcessStartWithMemoryState - change it to use cookie from memory state file instead of using cookie from snapshot xml - comments improvements - introduces new helpers to start and stop decompression process - reintroduces <externalSnapshot/> capability Pavel Hrdina (10): qemu_saveimage: extract starting process to qemuSaveImageStartProcess qemu_saveimage: introduce helpers to decompress memory state file qemu_saveimage: move qemuSaveImageStartProcess to qemu_process qemuProcessStartWithMemoryState: allow setting reason for audit log qemuProcessStartWithMemoryState: add snapshot argument qemuProcessStartWithMemoryState: make it possible to use without data qemu_snapshot: fix reverting external snapshot when not all disks are included qemu_snapshot: correctly load the saved memory state file capabilities: report full external snapshot support NEWS: document support for reverting external snapshots NEWS.rst | 8 + docs/formatcaps.rst | 7 + src/conf/capabilities.c | 1 + src/conf/capabilities.h | 1 + src/conf/schemas/capability.rng | 5 + src/qemu/qemu_capabilities.c | 1 + src/qemu/qemu_process.c | 95 +++++++++ src/qemu/qemu_process.h | 13 ++ src/qemu/qemu_saveimage.c | 189 +++++++++++------- src/qemu/qemu_saveimage.h | 15 ++ src/qemu/qemu_snapshot.c | 91 ++++++--- .../qemucaps2xmloutdata/caps.aarch64+hvf.xml | 1 + tests/qemucaps2xmloutdata/caps.aarch64.xml | 1 + tests/qemucaps2xmloutdata/caps.ppc.xml | 1 + tests/qemucaps2xmloutdata/caps.ppc64.xml | 1 + tests/qemucaps2xmloutdata/caps.riscv64.xml | 1 + tests/qemucaps2xmloutdata/caps.s390x.xml | 1 + tests/qemucaps2xmloutdata/caps.sparc.xml | 1 + tests/qemucaps2xmloutdata/caps.x86_64+hvf.xml | 1 + tests/qemucaps2xmloutdata/caps.x86_64.xml | 1 + 20 files changed, 328 insertions(+), 107 deletions(-) -- 2.41.0
Part of qemuSaveImageStartVM() function will be used when reverting external snapshots. To avoid duplicating code and logic extract the shared bits into separate function. Signed-off-by: Pavel Hrdina <phrdina@redhat.com> --- src/qemu/qemu_saveimage.c | 96 +++++++++++++++++++++++++++------------ src/qemu/qemu_saveimage.h | 11 +++++ 2 files changed, 78 insertions(+), 29 deletions(-) diff --git a/src/qemu/qemu_saveimage.c b/src/qemu/qemu_saveimage.c index XXXXXXX..XXXXXXX 100644 --- a/src/qemu/qemu_saveimage.c +++ b/src/qemu/qemu_saveimage.c @@ -XXX,XX +XXX,XX @@ qemuSaveImageOpen(virQEMUDriver *driver, return ret; } +/** + * qemuSaveImageStartProcess: + * @conn: connection object + * @driver: qemu driver object + * @vm: domain object + * @fd: FD pointer of memory state file + * @path: path to memory state file + * @data: data from memory state file + * @asyncJob: type of asynchronous job + * @start_flags: flags to start QEMU process with + * @started: boolean to store if QEMU process was started + * + * Start VM with existing memory state. Make sure that the stored memory state + * is correctly decompressed so it can be loaded by QEMU process. + * + * Returns 0 on success, -1 on error. + */ int -qemuSaveImageStartVM(virConnectPtr conn, - virQEMUDriver *driver, - virDomainObj *vm, - int *fd, - virQEMUSaveData *data, - const char *path, - bool start_paused, - bool reset_nvram, - virDomainAsyncJob asyncJob) +qemuSaveImageStartProcess(virConnectPtr conn, + virQEMUDriver *driver, + virDomainObj *vm, + int *fd, + const char *path, + virQEMUSaveData *data, + virDomainAsyncJob asyncJob, + unsigned int start_flags, + bool *started) { qemuDomainObjPrivate *priv = vm->privateData; - int ret = -1; - bool started = false; - virObjectEvent *event; + g_autoptr(qemuDomainSaveCookie) cookie = NULL; + virQEMUSaveHeader *header = &data->header; VIR_AUTOCLOSE intermediatefd = -1; g_autoptr(virCommand) cmd = NULL; g_autofree char *errbuf = NULL; - g_autoptr(virQEMUDriverConfig) cfg = virQEMUDriverGetConfig(driver); - virQEMUSaveHeader *header = &data->header; - g_autoptr(qemuDomainSaveCookie) cookie = NULL; int rc = 0; - unsigned int start_flags = VIR_QEMU_PROCESS_START_PAUSED | - VIR_QEMU_PROCESS_START_GEN_VMID; - - if (reset_nvram) - start_flags |= VIR_QEMU_PROCESS_START_RESET_NVRAM; if (virSaveCookieParseString(data->cookie, (virObject **)&cookie, virDomainXMLOptionGetSaveCookie(driver->xmlopt)) < 0) - goto cleanup; + return -1; if ((header->version == 2) && (header->compressed != QEMU_SAVE_FORMAT_RAW)) { if (!(cmd = qemuSaveImageGetCompressionCommand(header->compressed))) - goto cleanup; + return -1; intermediatefd = *fd; *fd = -1; @@ -XXX,XX +XXX,XX @@ qemuSaveImageStartVM(virConnectPtr conn, if (virCommandRunAsync(cmd, NULL) < 0) { *fd = intermediatefd; intermediatefd = -1; - goto cleanup; + return -1; } } @@ -XXX,XX +XXX,XX @@ qemuSaveImageStartVM(virConnectPtr conn, */ if (cookie && qemuDomainFixupCPUs(vm, &cookie->cpu) < 0) - goto cleanup; + return -1; if (cookie && !cookie->slirpHelper) priv->disableSlirp = true; @@ -XXX,XX +XXX,XX @@ qemuSaveImageStartVM(virConnectPtr conn, asyncJob, "stdio", *fd, path, NULL, VIR_NETDEV_VPORT_PROFILE_OP_RESTORE, start_flags) == 0) - started = true; + *started = true; if (intermediatefd != -1) { virErrorPtr orig_err = NULL; - if (!started) { + if (!*started) { /* if there was an error setting up qemu, the intermediate * process will wait forever to write to stdout, so we * must manually kill it and ignore any error related to @@ -XXX,XX +XXX,XX @@ qemuSaveImageStartVM(virConnectPtr conn, rc = -1; } - virDomainAuditStart(vm, "restored", started); - if (!started || rc < 0) - goto cleanup; + virDomainAuditStart(vm, "restored", *started); + if (!*started || rc < 0) + return -1; /* qemuProcessStart doesn't unset the qemu error reporting infrastructure * in case of migration (which is used in this case) so we need to reset it * so that the handle to virtlogd is not held open unnecessarily */ qemuMonitorSetDomainLog(qemuDomainGetMonitor(vm), NULL, NULL, NULL); + return 0; +} + +int +qemuSaveImageStartVM(virConnectPtr conn, + virQEMUDriver *driver, + virDomainObj *vm, + int *fd, + virQEMUSaveData *data, + const char *path, + bool start_paused, + bool reset_nvram, + virDomainAsyncJob asyncJob) +{ + int ret = -1; + bool started = false; + virObjectEvent *event; + g_autoptr(virQEMUDriverConfig) cfg = virQEMUDriverGetConfig(driver); + virQEMUSaveHeader *header = &data->header; + unsigned int start_flags = VIR_QEMU_PROCESS_START_PAUSED | + VIR_QEMU_PROCESS_START_GEN_VMID; + + if (reset_nvram) + start_flags |= VIR_QEMU_PROCESS_START_RESET_NVRAM; + + if (qemuSaveImageStartProcess(conn, driver, vm, fd, path, data, + asyncJob, start_flags, &started) < 0) { + goto cleanup; + } + event = virDomainEventLifecycleNewFromObj(vm, VIR_DOMAIN_EVENT_STARTED, VIR_DOMAIN_EVENT_STARTED_RESTORED); diff --git a/src/qemu/qemu_saveimage.h b/src/qemu/qemu_saveimage.h index XXXXXXX..XXXXXXX 100644 --- a/src/qemu/qemu_saveimage.h +++ b/src/qemu/qemu_saveimage.h @@ -XXX,XX +XXX,XX @@ qemuSaveImageUpdateDef(virQEMUDriver *driver, virDomainDef *def, const char *newxml); +int +qemuSaveImageStartProcess(virConnectPtr conn, + virQEMUDriver *driver, + virDomainObj *vm, + int *fd, + const char *path, + virQEMUSaveData *data, + virDomainAsyncJob asyncJob, + unsigned int start_flags, + bool *started); + int qemuSaveImageStartVM(virConnectPtr conn, virQEMUDriver *driver, -- 2.41.0
These new helpers separates the code from the logic used to start new QEMU process with memory state and will make it easier to move qemuSaveImageStartProcess() into qemu_process.c file. Signed-off-by: Pavel Hrdina <phrdina@redhat.com> --- src/qemu/qemu_saveimage.c | 155 +++++++++++++++++++++++++++----------- src/qemu/qemu_saveimage.h | 15 ++++ 2 files changed, 128 insertions(+), 42 deletions(-) diff --git a/src/qemu/qemu_saveimage.c b/src/qemu/qemu_saveimage.c index XXXXXXX..XXXXXXX 100644 --- a/src/qemu/qemu_saveimage.c +++ b/src/qemu/qemu_saveimage.c @@ -XXX,XX +XXX,XX @@ qemuSaveImageGetCompressionCommand(virQEMUSaveFormat compression) } +/** + * qemuSaveImageDecompressionStart: + * @data: data from memory state file + * @fd: pointer to FD of memory state file + * @intermediatefd: pointer to FD to store original @fd + * @errbuf: error buffer for @retcmd + * @retcmd: new virCommand pointer + * + * Start process to decompress VM memory state from @fd. If decompression + * is needed the original FD is stored to @intermediatefd and new FD after + * decompression is stored to @fd so caller can use the same variable + * in both cases. + * + * Function qemuSaveImageDecompressionStop() needs to be used to correctly + * stop the process and swap FD to the original state. + * + * Caller is responsible for freeing @retcmd. + * + * Returns -1 on error, 0 on success. + */ +int +qemuSaveImageDecompressionStart(virQEMUSaveData *data, + int *fd, + int *intermediatefd, + char **errbuf, + virCommand **retcmd) +{ + virQEMUSaveHeader *header = &data->header; + g_autoptr(virCommand) cmd = NULL; + + if (header->version != 2) + return 0; + + if (header->compressed == QEMU_SAVE_FORMAT_RAW) + return 0; + + if (!(cmd = qemuSaveImageGetCompressionCommand(header->compressed))) + return -1; + + *intermediatefd = *fd; + *fd = -1; + + virCommandSetInputFD(cmd, *intermediatefd); + virCommandSetOutputFD(cmd, fd); + virCommandSetErrorBuffer(cmd, errbuf); + virCommandDoAsyncIO(cmd); + + if (virCommandRunAsync(cmd, NULL) < 0) { + *fd = *intermediatefd; + *intermediatefd = -1; + return -1; + } + + *retcmd = g_steal_pointer(&cmd); + return 0; +} + + +/** + * qemuSaveImageDecompressionStop: + * @cmd: virCommand pointer + * @fd: pointer to FD of memory state file + * @intermediatefd: pointer to FD to store original @fd + * @errbuf: error buffer for @cmd + * @started: boolean to indicate if QEMU process was started + * @path: path to the memory state file + * + * Stop decompression process and close both @fd and @intermediatefd if + * necessary. + * + * Returns -1 on errro, 0 on success. + */ +int +qemuSaveImageDecompressionStop(virCommand *cmd, + int *fd, + int *intermediatefd, + char *errbuf, + bool started, + const char *path) +{ + int rc = 0; + virErrorPtr orig_err = NULL; + + if (*intermediatefd == -1) + return rc; + + if (!started) { + /* if there was an error setting up qemu, the intermediate + * process will wait forever to write to stdout, so we + * must manually kill it and ignore any error related to + * the process + */ + virErrorPreserveLast(&orig_err); + VIR_FORCE_CLOSE(*intermediatefd); + VIR_FORCE_CLOSE(*fd); + } + + rc = virCommandWait(cmd, NULL); + VIR_DEBUG("Decompression binary stderr: %s", NULLSTR(errbuf)); + virErrorRestore(&orig_err); + + if (VIR_CLOSE(*fd) < 0) { + virReportSystemError(errno, _("cannot close file: %1$s"), path); + rc = -1; + } + + return rc; +} + + /* Helper function to execute a migration to file with a correct save header * the caller needs to make sure that the processors are stopped and do all other * actions besides saving memory */ @@ -XXX,XX +XXX,XX @@ qemuSaveImageStartProcess(virConnectPtr conn, { qemuDomainObjPrivate *priv = vm->privateData; g_autoptr(qemuDomainSaveCookie) cookie = NULL; - virQEMUSaveHeader *header = &data->header; VIR_AUTOCLOSE intermediatefd = -1; g_autoptr(virCommand) cmd = NULL; g_autofree char *errbuf = NULL; @@ -XXX,XX +XXX,XX @@ qemuSaveImageStartProcess(virConnectPtr conn, virDomainXMLOptionGetSaveCookie(driver->xmlopt)) < 0) return -1; - if ((header->version == 2) && - (header->compressed != QEMU_SAVE_FORMAT_RAW)) { - if (!(cmd = qemuSaveImageGetCompressionCommand(header->compressed))) - return -1; - - intermediatefd = *fd; - *fd = -1; - - virCommandSetInputFD(cmd, intermediatefd); - virCommandSetOutputFD(cmd, fd); - virCommandSetErrorBuffer(cmd, &errbuf); - virCommandDoAsyncIO(cmd); - - if (virCommandRunAsync(cmd, NULL) < 0) { - *fd = intermediatefd; - intermediatefd = -1; - return -1; - } - } + if (qemuSaveImageDecompressionStart(data, fd, &intermediatefd, &errbuf, &cmd) < 0) + return -1; /* No cookie means libvirt which saved the domain was too old to mess up * the CPU definitions. @@ -XXX,XX +XXX,XX @@ qemuSaveImageStartProcess(virConnectPtr conn, start_flags) == 0) *started = true; - if (intermediatefd != -1) { - virErrorPtr orig_err = NULL; - - if (!*started) { - /* if there was an error setting up qemu, the intermediate - * process will wait forever to write to stdout, so we - * must manually kill it and ignore any error related to - * the process - */ - virErrorPreserveLast(&orig_err); - VIR_FORCE_CLOSE(intermediatefd); - VIR_FORCE_CLOSE(*fd); - } - - rc = virCommandWait(cmd, NULL); - VIR_DEBUG("Decompression binary stderr: %s", NULLSTR(errbuf)); - virErrorRestore(&orig_err); - } - if (VIR_CLOSE(*fd) < 0) { - virReportSystemError(errno, _("cannot close file: %1$s"), path); - rc = -1; - } + rc = qemuSaveImageDecompressionStop(cmd, fd, &intermediatefd, errbuf, *started, path); virDomainAuditStart(vm, "restored", *started); if (!*started || rc < 0) diff --git a/src/qemu/qemu_saveimage.h b/src/qemu/qemu_saveimage.h index XXXXXXX..XXXXXXX 100644 --- a/src/qemu/qemu_saveimage.h +++ b/src/qemu/qemu_saveimage.h @@ -XXX,XX +XXX,XX @@ qemuSaveImageGetCompressionProgram(const char *imageFormat, bool use_raw_on_fail) ATTRIBUTE_NONNULL(2); +int +qemuSaveImageDecompressionStart(virQEMUSaveData *data, + int *fd, + int *intermediatefd, + char **errbuf, + virCommand **retcmd); + +int +qemuSaveImageDecompressionStop(virCommand *cmd, + int *fd, + int *intermediatefd, + char *errbuf, + bool started, + const char *path); + int qemuSaveImageCreate(virQEMUDriver *driver, virDomainObj *vm, -- 2.41.0
The function will no longer be used only when restoring VM as it will be used when reverting snapshot as well so move it to qemu_process and rename it accordingly. Signed-off-by: Pavel Hrdina <phrdina@redhat.com> --- src/qemu/qemu_process.c | 73 +++++++++++++++++++++++++++++++++++++ src/qemu/qemu_process.h | 11 ++++++ src/qemu/qemu_saveimage.c | 75 ++------------------------------------- src/qemu/qemu_saveimage.h | 11 ------ 4 files changed, 86 insertions(+), 84 deletions(-) diff --git a/src/qemu/qemu_process.c b/src/qemu/qemu_process.c index XXXXXXX..XXXXXXX 100644 --- a/src/qemu/qemu_process.c +++ b/src/qemu/qemu_process.c @@ -XXX,XX +XXX,XX @@ qemuProcessStart(virConnectPtr conn, } +/** + * qemuProcessStartWithMemoryState: + * @conn: connection object + * @driver: qemu driver object + * @vm: domain object + * @fd: FD pointer of memory state file + * @path: path to memory state file + * @data: data from memory state file + * @asyncJob: type of asynchronous job + * @start_flags: flags to start QEMU process with + * @started: boolean to store if QEMU process was started + * + * Start VM with existing memory state. Make sure that the stored memory state + * is correctly decompressed so it can be loaded by QEMU process. + * + * Returns 0 on success, -1 on error. + */ +int +qemuProcessStartWithMemoryState(virConnectPtr conn, + virQEMUDriver *driver, + virDomainObj *vm, + int *fd, + const char *path, + virQEMUSaveData *data, + virDomainAsyncJob asyncJob, + unsigned int start_flags, + bool *started) +{ + qemuDomainObjPrivate *priv = vm->privateData; + g_autoptr(qemuDomainSaveCookie) cookie = NULL; + VIR_AUTOCLOSE intermediatefd = -1; + g_autoptr(virCommand) cmd = NULL; + g_autofree char *errbuf = NULL; + int rc = 0; + + if (virSaveCookieParseString(data->cookie, (virObject **)&cookie, + virDomainXMLOptionGetSaveCookie(driver->xmlopt)) < 0) + return -1; + + if (qemuSaveImageDecompressionStart(data, fd, &intermediatefd, &errbuf, &cmd) < 0) + return -1; + + /* No cookie means libvirt which saved the domain was too old to mess up + * the CPU definitions. + */ + if (cookie && + qemuDomainFixupCPUs(vm, &cookie->cpu) < 0) + return -1; + + if (cookie && !cookie->slirpHelper) + priv->disableSlirp = true; + + if (qemuProcessStart(conn, driver, vm, cookie ? cookie->cpu : NULL, + asyncJob, "stdio", *fd, path, NULL, + VIR_NETDEV_VPORT_PROFILE_OP_RESTORE, + start_flags) == 0) + *started = true; + + rc = qemuSaveImageDecompressionStop(cmd, fd, &intermediatefd, errbuf, *started, path); + + virDomainAuditStart(vm, "restored", *started); + if (!*started || rc < 0) + return -1; + + /* qemuProcessStart doesn't unset the qemu error reporting infrastructure + * in case of migration (which is used in this case) so we need to reset it + * so that the handle to virtlogd is not held open unnecessarily */ + qemuMonitorSetDomainLog(qemuDomainGetMonitor(vm), NULL, NULL, NULL); + + return 0; +} + + int qemuProcessCreatePretendCmdPrepare(virQEMUDriver *driver, virDomainObj *vm, diff --git a/src/qemu/qemu_process.h b/src/qemu/qemu_process.h index XXXXXXX..XXXXXXX 100644 --- a/src/qemu/qemu_process.h +++ b/src/qemu/qemu_process.h @@ -XXX,XX +XXX,XX @@ #include "qemu_conf.h" #include "qemu_domain.h" +#include "qemu_saveimage.h" #include "vireventthread.h" int qemuProcessPrepareMonitorChr(virDomainChrSourceDef *monConfig, @@ -XXX,XX +XXX,XX @@ int qemuProcessStart(virConnectPtr conn, virNetDevVPortProfileOp vmop, unsigned int flags); +int qemuProcessStartWithMemoryState(virConnectPtr conn, + virQEMUDriver *driver, + virDomainObj *vm, + int *fd, + const char *path, + virQEMUSaveData *data, + virDomainAsyncJob asyncJob, + unsigned int start_flags, + bool *started); + int qemuProcessCreatePretendCmdPrepare(virQEMUDriver *driver, virDomainObj *vm, const char *migrateURI, diff --git a/src/qemu/qemu_saveimage.c b/src/qemu/qemu_saveimage.c index XXXXXXX..XXXXXXX 100644 --- a/src/qemu/qemu_saveimage.c +++ b/src/qemu/qemu_saveimage.c @@ -XXX,XX +XXX,XX @@ qemuSaveImageOpen(virQEMUDriver *driver, return ret; } -/** - * qemuSaveImageStartProcess: - * @conn: connection object - * @driver: qemu driver object - * @vm: domain object - * @fd: FD pointer of memory state file - * @path: path to memory state file - * @data: data from memory state file - * @asyncJob: type of asynchronous job - * @start_flags: flags to start QEMU process with - * @started: boolean to store if QEMU process was started - * - * Start VM with existing memory state. Make sure that the stored memory state - * is correctly decompressed so it can be loaded by QEMU process. - * - * Returns 0 on success, -1 on error. - */ -int -qemuSaveImageStartProcess(virConnectPtr conn, - virQEMUDriver *driver, - virDomainObj *vm, - int *fd, - const char *path, - virQEMUSaveData *data, - virDomainAsyncJob asyncJob, - unsigned int start_flags, - bool *started) -{ - qemuDomainObjPrivate *priv = vm->privateData; - g_autoptr(qemuDomainSaveCookie) cookie = NULL; - VIR_AUTOCLOSE intermediatefd = -1; - g_autoptr(virCommand) cmd = NULL; - g_autofree char *errbuf = NULL; - int rc = 0; - - if (virSaveCookieParseString(data->cookie, (virObject **)&cookie, - virDomainXMLOptionGetSaveCookie(driver->xmlopt)) < 0) - return -1; - - if (qemuSaveImageDecompressionStart(data, fd, &intermediatefd, &errbuf, &cmd) < 0) - return -1; - - /* No cookie means libvirt which saved the domain was too old to mess up - * the CPU definitions. - */ - if (cookie && - qemuDomainFixupCPUs(vm, &cookie->cpu) < 0) - return -1; - - if (cookie && !cookie->slirpHelper) - priv->disableSlirp = true; - - if (qemuProcessStart(conn, driver, vm, cookie ? cookie->cpu : NULL, - asyncJob, "stdio", *fd, path, NULL, - VIR_NETDEV_VPORT_PROFILE_OP_RESTORE, - start_flags) == 0) - *started = true; - - rc = qemuSaveImageDecompressionStop(cmd, fd, &intermediatefd, errbuf, *started, path); - - virDomainAuditStart(vm, "restored", *started); - if (!*started || rc < 0) - return -1; - - /* qemuProcessStart doesn't unset the qemu error reporting infrastructure - * in case of migration (which is used in this case) so we need to reset it - * so that the handle to virtlogd is not held open unnecessarily */ - qemuMonitorSetDomainLog(qemuDomainGetMonitor(vm), NULL, NULL, NULL); - - return 0; -} int qemuSaveImageStartVM(virConnectPtr conn, @@ -XXX,XX +XXX,XX @@ qemuSaveImageStartVM(virConnectPtr conn, if (reset_nvram) start_flags |= VIR_QEMU_PROCESS_START_RESET_NVRAM; - if (qemuSaveImageStartProcess(conn, driver, vm, fd, path, data, - asyncJob, start_flags, &started) < 0) { + if (qemuProcessStartWithMemoryState(conn, driver, vm, fd, path, data, + asyncJob, start_flags, &started) < 0) { goto cleanup; } diff --git a/src/qemu/qemu_saveimage.h b/src/qemu/qemu_saveimage.h index XXXXXXX..XXXXXXX 100644 --- a/src/qemu/qemu_saveimage.h +++ b/src/qemu/qemu_saveimage.h @@ -XXX,XX +XXX,XX @@ qemuSaveImageUpdateDef(virQEMUDriver *driver, virDomainDef *def, const char *newxml); -int -qemuSaveImageStartProcess(virConnectPtr conn, - virQEMUDriver *driver, - virDomainObj *vm, - int *fd, - const char *path, - virQEMUSaveData *data, - virDomainAsyncJob asyncJob, - unsigned int start_flags, - bool *started); - int qemuSaveImageStartVM(virConnectPtr conn, virQEMUDriver *driver, -- 2.41.0
When called by snapshot code we will need to use different reason. Signed-off-by: Pavel Hrdina <phrdina@redhat.com> Reviewed-by: Peter Krempa <pkrempa@redhat.com> --- src/qemu/qemu_process.c | 6 +++++- src/qemu/qemu_process.h | 1 + src/qemu/qemu_saveimage.c | 3 ++- 3 files changed, 8 insertions(+), 2 deletions(-) diff --git a/src/qemu/qemu_process.c b/src/qemu/qemu_process.c index XXXXXXX..XXXXXXX 100644 --- a/src/qemu/qemu_process.c +++ b/src/qemu/qemu_process.c @@ -XXX,XX +XXX,XX @@ qemuProcessStart(virConnectPtr conn, * @data: data from memory state file * @asyncJob: type of asynchronous job * @start_flags: flags to start QEMU process with + * @reason: audit log reason * @started: boolean to store if QEMU process was started * * Start VM with existing memory state. Make sure that the stored memory state * is correctly decompressed so it can be loaded by QEMU process. * + * For audit purposes the expected @reason is one of `restored` or `from-snapshot`. + * * Returns 0 on success, -1 on error. */ int @@ -XXX,XX +XXX,XX @@ qemuProcessStartWithMemoryState(virConnectPtr conn, virQEMUSaveData *data, virDomainAsyncJob asyncJob, unsigned int start_flags, + const char *reason, bool *started) { qemuDomainObjPrivate *priv = vm->privateData; @@ -XXX,XX +XXX,XX @@ qemuProcessStartWithMemoryState(virConnectPtr conn, rc = qemuSaveImageDecompressionStop(cmd, fd, &intermediatefd, errbuf, *started, path); - virDomainAuditStart(vm, "restored", *started); + virDomainAuditStart(vm, reason, *started); if (!*started || rc < 0) return -1; diff --git a/src/qemu/qemu_process.h b/src/qemu/qemu_process.h index XXXXXXX..XXXXXXX 100644 --- a/src/qemu/qemu_process.h +++ b/src/qemu/qemu_process.h @@ -XXX,XX +XXX,XX @@ int qemuProcessStartWithMemoryState(virConnectPtr conn, virQEMUSaveData *data, virDomainAsyncJob asyncJob, unsigned int start_flags, + const char *reason, bool *started); int qemuProcessCreatePretendCmdPrepare(virQEMUDriver *driver, diff --git a/src/qemu/qemu_saveimage.c b/src/qemu/qemu_saveimage.c index XXXXXXX..XXXXXXX 100644 --- a/src/qemu/qemu_saveimage.c +++ b/src/qemu/qemu_saveimage.c @@ -XXX,XX +XXX,XX @@ qemuSaveImageStartVM(virConnectPtr conn, start_flags |= VIR_QEMU_PROCESS_START_RESET_NVRAM; if (qemuProcessStartWithMemoryState(conn, driver, vm, fd, path, data, - asyncJob, start_flags, &started) < 0) { + asyncJob, start_flags, "restored", + &started) < 0) { goto cleanup; } -- 2.41.0
When called from snapshot code we will need to pass snapshot object in order to make internal snapshots work correctly. Signed-off-by: Pavel Hrdina <phrdina@redhat.com> --- src/qemu/qemu_process.c | 9 ++++++++- src/qemu/qemu_process.h | 1 + src/qemu/qemu_saveimage.c | 2 +- 3 files changed, 10 insertions(+), 2 deletions(-) diff --git a/src/qemu/qemu_process.c b/src/qemu/qemu_process.c index XXXXXXX..XXXXXXX 100644 --- a/src/qemu/qemu_process.c +++ b/src/qemu/qemu_process.c @@ -XXX,XX +XXX,XX @@ qemuProcessStart(virConnectPtr conn, * @vm: domain object * @fd: FD pointer of memory state file * @path: path to memory state file + * @snapshot: internal snapshot to load when starting QEMU process or NULL * @data: data from memory state file * @asyncJob: type of asynchronous job * @start_flags: flags to start QEMU process with @@ -XXX,XX +XXX,XX @@ qemuProcessStart(virConnectPtr conn, * Start VM with existing memory state. Make sure that the stored memory state * is correctly decompressed so it can be loaded by QEMU process. * + * When reverting to internal snapshot callers needs to pass @snapshot as well + * correctly start QEMU process. + * + * When restoring VM from saved image @snapshot needs to be NULL. + * * For audit purposes the expected @reason is one of `restored` or `from-snapshot`. * * Returns 0 on success, -1 on error. @@ -XXX,XX +XXX,XX @@ qemuProcessStartWithMemoryState(virConnectPtr conn, virDomainObj *vm, int *fd, const char *path, + virDomainMomentObj *snapshot, virQEMUSaveData *data, virDomainAsyncJob asyncJob, unsigned int start_flags, @@ -XXX,XX +XXX,XX @@ qemuProcessStartWithMemoryState(virConnectPtr conn, priv->disableSlirp = true; if (qemuProcessStart(conn, driver, vm, cookie ? cookie->cpu : NULL, - asyncJob, "stdio", *fd, path, NULL, + asyncJob, "stdio", *fd, path, snapshot, VIR_NETDEV_VPORT_PROFILE_OP_RESTORE, start_flags) == 0) *started = true; diff --git a/src/qemu/qemu_process.h b/src/qemu/qemu_process.h index XXXXXXX..XXXXXXX 100644 --- a/src/qemu/qemu_process.h +++ b/src/qemu/qemu_process.h @@ -XXX,XX +XXX,XX @@ int qemuProcessStartWithMemoryState(virConnectPtr conn, virDomainObj *vm, int *fd, const char *path, + virDomainMomentObj *snapshot, virQEMUSaveData *data, virDomainAsyncJob asyncJob, unsigned int start_flags, diff --git a/src/qemu/qemu_saveimage.c b/src/qemu/qemu_saveimage.c index XXXXXXX..XXXXXXX 100644 --- a/src/qemu/qemu_saveimage.c +++ b/src/qemu/qemu_saveimage.c @@ -XXX,XX +XXX,XX @@ qemuSaveImageStartVM(virConnectPtr conn, if (reset_nvram) start_flags |= VIR_QEMU_PROCESS_START_RESET_NVRAM; - if (qemuProcessStartWithMemoryState(conn, driver, vm, fd, path, data, + if (qemuProcessStartWithMemoryState(conn, driver, vm, fd, path, NULL, data, asyncJob, start_flags, "restored", &started) < 0) { goto cleanup; -- 2.41.0
When used with internal snapshots there is no memory state file so we have no data to load and decompression is not needed. Signed-off-by: Pavel Hrdina <phrdina@redhat.com> --- src/qemu/qemu_process.c | 23 +++++++++++++++++------ 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/src/qemu/qemu_process.c b/src/qemu/qemu_process.c index XXXXXXX..XXXXXXX 100644 --- a/src/qemu/qemu_process.c +++ b/src/qemu/qemu_process.c @@ -XXX,XX +XXX,XX @@ qemuProcessStart(virConnectPtr conn, * @fd: FD pointer of memory state file * @path: path to memory state file * @snapshot: internal snapshot to load when starting QEMU process or NULL - * @data: data from memory state file + * @data: data from memory state file or NULL * @asyncJob: type of asynchronous job * @start_flags: flags to start QEMU process with * @reason: audit log reason @@ -XXX,XX +XXX,XX @@ qemuProcessStart(virConnectPtr conn, * is correctly decompressed so it can be loaded by QEMU process. * * When reverting to internal snapshot callers needs to pass @snapshot as well - * correctly start QEMU process. + * correctly start QEMU process. In addition there is no memory state file so + * it's safe to pass NULL as @data. * * When restoring VM from saved image @snapshot needs to be NULL. * @@ -XXX,XX +XXX,XX @@ qemuProcessStartWithMemoryState(virConnectPtr conn, g_autofree char *errbuf = NULL; int rc = 0; - if (virSaveCookieParseString(data->cookie, (virObject **)&cookie, - virDomainXMLOptionGetSaveCookie(driver->xmlopt)) < 0) - return -1; + if (data) { + if (virSaveCookieParseString(data->cookie, (virObject **)&cookie, + virDomainXMLOptionGetSaveCookie(driver->xmlopt)) < 0) + return -1; + + if (qemuSaveImageDecompressionStart(data, fd, &intermediatefd, + &errbuf, &cmd) < 0) { + return -1; + } + } if (qemuSaveImageDecompressionStart(data, fd, &intermediatefd, &errbuf, &cmd) < 0) return -1; @@ -XXX,XX +XXX,XX @@ qemuProcessStartWithMemoryState(virConnectPtr conn, start_flags) == 0) *started = true; - rc = qemuSaveImageDecompressionStop(cmd, fd, &intermediatefd, errbuf, *started, path); + if (data) { + rc = qemuSaveImageDecompressionStop(cmd, fd, &intermediatefd, errbuf, + *started, path); + } virDomainAuditStart(vm, reason, *started); if (!*started || rc < 0) -- 2.41.0
We need to skip all disks that have snapshot type other than 'external'. Signed-off-by: Pavel Hrdina <phrdina@redhat.com> --- src/qemu/qemu_snapshot.c | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/qemu/qemu_snapshot.c b/src/qemu/qemu_snapshot.c index XXXXXXX..XXXXXXX 100644 --- a/src/qemu/qemu_snapshot.c +++ b/src/qemu/qemu_snapshot.c @@ -XXX,XX +XXX,XX @@ qemuSnapshotRevertExternalPrepare(virDomainObj *vm, virDomainSnapshotDiskDef *snapdisk = &tmpsnapdef->disks[i]; virDomainDiskDef *domdisk = domdef->disks[i]; + if (snapdisk->snapshot != VIR_DOMAIN_SNAPSHOT_LOCATION_EXTERNAL) + continue; + if (qemuSnapshotPrepareDiskExternal(domdisk, snapdisk, active, false) < 0) return -1; } @@ -XXX,XX +XXX,XX @@ qemuSnapshotRevertExternalActive(virDomainObj *vm, return -1; for (i = 0; i < tmpsnapdef->ndisks; i++) { + if (tmpsnapdef->disks[i].snapshot != VIR_DOMAIN_SNAPSHOT_LOCATION_EXTERNAL) + continue; + if (qemuSnapshotDiskPrepareOne(snapctxt, vm->def->disks[i], tmpsnapdef->disks + i, @@ -XXX,XX +XXX,XX @@ qemuSnapshotRevertExternalFinish(virDomainObj *vm, for (i = 0; i < curdef->nrevertdisks; i++) { virDomainSnapshotDiskDef *snapdisk = &(curdef->revertdisks[i]); + if (snapdisk->snapshot != VIR_DOMAIN_SNAPSHOT_LOCATION_EXTERNAL) + continue; + if (virStorageSourceInit(snapdisk->src) < 0 || virStorageSourceUnlink(snapdisk->src) < 0) { VIR_WARN("Failed to remove snapshot image '%s'", @@ -XXX,XX +XXX,XX @@ qemuSnapshotRevertExternalFinish(virDomainObj *vm, for (i = 0; i < curdef->ndisks; i++) { virDomainSnapshotDiskDef *snapdisk = &(curdef->disks[i]); + if (snapdisk->snapshot != VIR_DOMAIN_SNAPSHOT_LOCATION_EXTERNAL) + continue; + if (virStorageSourceInit(snapdisk->src) < 0 || virStorageSourceUnlink(snapdisk->src) < 0) { VIR_WARN("Failed to remove snapshot image '%s'", -- 2.41.0
Original code assumed that the memory state file is only migration stream but it has additional metadata stored by libvirt. To correctly load the memory state file we need to reuse code that is used when restoring domain from saved image. Signed-off-by: Pavel Hrdina <phrdina@redhat.com> --- src/qemu/qemu_snapshot.c | 79 ++++++++++++++++++++++++---------------- 1 file changed, 47 insertions(+), 32 deletions(-) diff --git a/src/qemu/qemu_snapshot.c b/src/qemu/qemu_snapshot.c index XXXXXXX..XXXXXXX 100644 --- a/src/qemu/qemu_snapshot.c +++ b/src/qemu/qemu_snapshot.c @@ -XXX,XX +XXX,XX @@ qemuSnapshotRevertWriteMetadata(virDomainObj *vm, } +typedef struct _qemuSnapshotRevertMemoryData { + int fd; + char *path; + virQEMUSaveData *data; +} qemuSnapshotRevertMemoryData; + +static void +qemuSnapshotClearRevertMemoryData(qemuSnapshotRevertMemoryData *memdata) +{ + VIR_FORCE_CLOSE(memdata->fd); + virQEMUSaveDataFree(memdata->data); +} +G_DEFINE_AUTO_CLEANUP_CLEAR_FUNC(qemuSnapshotRevertMemoryData, qemuSnapshotClearRevertMemoryData); + + /** * qemuSnapshotRevertExternalPrepare: * @vm: domain object @@ -XXX,XX +XXX,XX @@ qemuSnapshotRevertWriteMetadata(virDomainObj *vm, * @snap: snapshot object we are reverting to * @config: live domain definition * @inactiveConfig: offline domain definition - * memsnapFD: pointer to store memory state file FD or NULL - * memsnapPath: pointer to store memory state file path or NULL + * @memdata: struct with data to load memory state * * Prepare new temporary snapshot definition @tmpsnapdef that will * be used while creating new overlay files after reverting to snapshot * @snap. In case we are reverting to snapshot with memory state it will - * open it and pass FD via @memsnapFD and path to the file via - * @memsnapPath, caller is responsible for freeing both @memsnapFD and - * memsnapPath. + * open it and store necessary data in @memdata. Caller is responsible + * to clear the data by using qemuSnapshotClearRevertMemoryData(). * * Returns 0 in success, -1 on error. */ @@ -XXX,XX +XXX,XX @@ qemuSnapshotRevertExternalPrepare(virDomainObj *vm, virDomainMomentObj *snap, virDomainDef *config, virDomainDef *inactiveConfig, - int *memsnapFD, - char **memsnapPath) + qemuSnapshotRevertMemoryData *memdata) { size_t i; bool active = virDomainObjIsActive(vm); @@ -XXX,XX +XXX,XX @@ qemuSnapshotRevertExternalPrepare(virDomainObj *vm, return -1; } - if (memsnapFD && memsnapPath && snapdef->memorysnapshotfile) { + if (memdata && snapdef->memorysnapshotfile) { virQEMUDriver *driver = ((qemuDomainObjPrivate *) vm->privateData)->driver; - g_autoptr(virQEMUDriverConfig) cfg = virQEMUDriverGetConfig(driver); + g_autoptr(virDomainDef) savedef = NULL; - *memsnapPath = snapdef->memorysnapshotfile; - *memsnapFD = qemuDomainOpenFile(cfg, NULL, *memsnapPath, O_RDONLY, NULL); + memdata->path = snapdef->memorysnapshotfile; + memdata->fd = qemuSaveImageOpen(driver, NULL, memdata->path, + &savedef, &memdata->data, + false, NULL, + false, false); + + if (memdata->fd < 0) + return -1; + + if (!virDomainDefCheckABIStability(savedef, domdef, driver->xmlopt)) + return -1; } return 0; @@ -XXX,XX +XXX,XX @@ qemuSnapshotRevertActive(virDomainObj *vm, virObjectEvent *event = NULL; virObjectEvent *event2 = NULL; virDomainMomentObj *loadSnap = NULL; - VIR_AUTOCLOSE memsnapFD = -1; - char *memsnapPath = NULL; int detail; bool defined = false; - qemuDomainSaveCookie *cookie = (qemuDomainSaveCookie *) snapdef->cookie; int rc; g_autoptr(virDomainSnapshotDef) tmpsnapdef = NULL; + g_auto(qemuSnapshotRevertMemoryData) memdata = { -1, NULL, NULL }; + bool started = false; start_flags |= VIR_QEMU_PROCESS_START_PAUSED; @@ -XXX,XX +XXX,XX @@ qemuSnapshotRevertActive(virDomainObj *vm, if (qemuSnapshotRevertExternalPrepare(vm, tmpsnapdef, snap, *config, *inactiveConfig, - &memsnapFD, &memsnapPath) < 0) { + &memdata) < 0) { return -1; } } else { @@ -XXX,XX +XXX,XX @@ qemuSnapshotRevertActive(virDomainObj *vm, virDomainObjAssignDef(vm, config, true, NULL); - /* No cookie means libvirt which saved the domain was too old to - * mess up the CPU definitions. - */ - if (cookie && - qemuDomainFixupCPUs(vm, &cookie->cpu) < 0) + if (qemuProcessStartWithMemoryState(snapshot->domain->conn, driver, vm, + &memdata.fd, memdata.path, loadSnap, + memdata.data, VIR_ASYNC_JOB_SNAPSHOT, + start_flags, "from-snapshot", + &started) < 0) { + if (started) { + qemuProcessStop(driver, vm, VIR_DOMAIN_SHUTOFF_FAILED, + VIR_ASYNC_JOB_SNAPSHOT, + VIR_QEMU_PROCESS_STOP_MIGRATED); + } return -1; + } - rc = qemuProcessStart(snapshot->domain->conn, driver, vm, - cookie ? cookie->cpu : NULL, - VIR_ASYNC_JOB_SNAPSHOT, NULL, memsnapFD, - memsnapPath, loadSnap, - VIR_NETDEV_VPORT_PROFILE_OP_CREATE, - start_flags); - virDomainAuditStart(vm, "from-snapshot", rc >= 0); detail = VIR_DOMAIN_EVENT_STARTED_FROM_SNAPSHOT; event = virDomainEventLifecycleNewFromObj(vm, VIR_DOMAIN_EVENT_STARTED, detail); virObjectEventStateQueue(driver->domainEventState, event); - if (rc < 0) - return -1; - if (virDomainSnapshotIsExternal(snap)) { if (qemuSnapshotRevertExternalActive(vm, tmpsnapdef) < 0) @@ -XXX,XX +XXX,XX @@ qemuSnapshotRevertInactive(virDomainObj *vm, return -1; if (qemuSnapshotRevertExternalPrepare(vm, tmpsnapdef, snap, - NULL, *inactiveConfig, - NULL, NULL) < 0) { + NULL, *inactiveConfig, NULL) < 0) { return -1; } -- 2.41.0
Now that deleting and reverting external snapshots is implemented we can report that in capabilities so management applications can use that information and start using external snapshots. Signed-off-by: Pavel Hrdina <phrdina@redhat.com> Reviewed-by: Peter Krempa <pkrempa@redhat.com> --- docs/formatcaps.rst | 7 +++++++ src/conf/capabilities.c | 1 + src/conf/capabilities.h | 1 + src/conf/schemas/capability.rng | 5 +++++ src/qemu/qemu_capabilities.c | 1 + tests/qemucaps2xmloutdata/caps.aarch64+hvf.xml | 1 + tests/qemucaps2xmloutdata/caps.aarch64.xml | 1 + tests/qemucaps2xmloutdata/caps.ppc.xml | 1 + tests/qemucaps2xmloutdata/caps.ppc64.xml | 1 + tests/qemucaps2xmloutdata/caps.riscv64.xml | 1 + tests/qemucaps2xmloutdata/caps.s390x.xml | 1 + tests/qemucaps2xmloutdata/caps.sparc.xml | 1 + tests/qemucaps2xmloutdata/caps.x86_64+hvf.xml | 1 + tests/qemucaps2xmloutdata/caps.x86_64.xml | 1 + 14 files changed, 24 insertions(+) diff --git a/docs/formatcaps.rst b/docs/formatcaps.rst index XXXXXXX..XXXXXXX 100644 --- a/docs/formatcaps.rst +++ b/docs/formatcaps.rst @@ -XXX,XX +XXX,XX @@ The ``<guest/>`` element will typically wrap up the following elements: creating external disk snapshots is supported. If absent, creating external snapshots may still be supported, but it requires attempting the API and checking for an error to find out for sure. :since:`Since 1.2.3` + ``externalSnapshot`` + If this element is present, the hypervisor supports deleting and + reverting external snapshots including memory state. Support for creation + of external snapshots is reported via the ``disksnapshot`` feature flag. + Management applications can now switch from internal snapshots to external + snapshots. :since:`Since 9.7.0` Examples ~~~~~~~~ @@ -XXX,XX +XXX,XX @@ capabilities enabled in the chip and BIOS you will see: <cpuselection/> <deviceboot/> <disksnapshot default='on' toggle='no'/> + <externalSnapshot/> </features> </guest> diff --git a/src/conf/capabilities.c b/src/conf/capabilities.c index XXXXXXX..XXXXXXX 100644 --- a/src/conf/capabilities.c +++ b/src/conf/capabilities.c @@ -XXX,XX +XXX,XX @@ static const struct virCapsGuestFeatureInfo virCapsGuestFeatureInfos[VIR_CAPS_GU [VIR_CAPS_GUEST_FEATURE_TYPE_DEVICEBOOT] = { "deviceboot", false }, [VIR_CAPS_GUEST_FEATURE_TYPE_DISKSNAPSHOT] = { "disksnapshot", true }, [VIR_CAPS_GUEST_FEATURE_TYPE_HAP] = { "hap", true }, + [VIR_CAPS_GUEST_FEATURE_TYPE_EXTERNAL_SNAPSHOT] = { "externalSnapshot", false }, }; diff --git a/src/conf/capabilities.h b/src/conf/capabilities.h index XXXXXXX..XXXXXXX 100644 --- a/src/conf/capabilities.h +++ b/src/conf/capabilities.h @@ -XXX,XX +XXX,XX @@ typedef enum { VIR_CAPS_GUEST_FEATURE_TYPE_DEVICEBOOT, VIR_CAPS_GUEST_FEATURE_TYPE_DISKSNAPSHOT, VIR_CAPS_GUEST_FEATURE_TYPE_HAP, + VIR_CAPS_GUEST_FEATURE_TYPE_EXTERNAL_SNAPSHOT, VIR_CAPS_GUEST_FEATURE_TYPE_LAST } virCapsGuestFeatureType; diff --git a/src/conf/schemas/capability.rng b/src/conf/schemas/capability.rng index XXXXXXX..XXXXXXX 100644 --- a/src/conf/schemas/capability.rng +++ b/src/conf/schemas/capability.rng @@ -XXX,XX +XXX,XX @@ <empty/> </element> </optional> + <optional> + <element name="externalSnapshot"> + <empty/> + </element> + </optional> </interleave> </element> </define> diff --git a/src/qemu/qemu_capabilities.c b/src/qemu/qemu_capabilities.c index XXXXXXX..XXXXXXX 100644 --- a/src/qemu/qemu_capabilities.c +++ b/src/qemu/qemu_capabilities.c @@ -XXX,XX +XXX,XX @@ virQEMUCapsInitGuestFromBinary(virCaps *caps, virCapabilitiesAddGuestFeature(guest, VIR_CAPS_GUEST_FEATURE_TYPE_DEVICEBOOT); virCapabilitiesAddGuestFeatureWithToggle(guest, VIR_CAPS_GUEST_FEATURE_TYPE_DISKSNAPSHOT, true, false); + virCapabilitiesAddGuestFeature(guest, VIR_CAPS_GUEST_FEATURE_TYPE_EXTERNAL_SNAPSHOT); if (virQEMUCapsGet(qemuCaps, QEMU_CAPS_TCG)) { virCapabilitiesAddGuestDomain(guest, VIR_DOMAIN_VIRT_QEMU, diff --git a/tests/qemucaps2xmloutdata/caps.aarch64+hvf.xml b/tests/qemucaps2xmloutdata/caps.aarch64+hvf.xml index XXXXXXX..XXXXXXX 100644 --- a/tests/qemucaps2xmloutdata/caps.aarch64+hvf.xml +++ b/tests/qemucaps2xmloutdata/caps.aarch64+hvf.xml @@ -XXX,XX +XXX,XX @@ <cpuselection/> <deviceboot/> <disksnapshot default='on' toggle='no'/> + <externalSnapshot/> </features> </guest> diff --git a/tests/qemucaps2xmloutdata/caps.aarch64.xml b/tests/qemucaps2xmloutdata/caps.aarch64.xml index XXXXXXX..XXXXXXX 100644 --- a/tests/qemucaps2xmloutdata/caps.aarch64.xml +++ b/tests/qemucaps2xmloutdata/caps.aarch64.xml @@ -XXX,XX +XXX,XX @@ <cpuselection/> <deviceboot/> <disksnapshot default='on' toggle='no'/> + <externalSnapshot/> </features> </guest> diff --git a/tests/qemucaps2xmloutdata/caps.ppc.xml b/tests/qemucaps2xmloutdata/caps.ppc.xml index XXXXXXX..XXXXXXX 100644 --- a/tests/qemucaps2xmloutdata/caps.ppc.xml +++ b/tests/qemucaps2xmloutdata/caps.ppc.xml @@ -XXX,XX +XXX,XX @@ <cpuselection/> <deviceboot/> <disksnapshot default='on' toggle='no'/> + <externalSnapshot/> </features> </guest> diff --git a/tests/qemucaps2xmloutdata/caps.ppc64.xml b/tests/qemucaps2xmloutdata/caps.ppc64.xml index XXXXXXX..XXXXXXX 100644 --- a/tests/qemucaps2xmloutdata/caps.ppc64.xml +++ b/tests/qemucaps2xmloutdata/caps.ppc64.xml @@ -XXX,XX +XXX,XX @@ <cpuselection/> <deviceboot/> <disksnapshot default='on' toggle='no'/> + <externalSnapshot/> </features> </guest> diff --git a/tests/qemucaps2xmloutdata/caps.riscv64.xml b/tests/qemucaps2xmloutdata/caps.riscv64.xml index XXXXXXX..XXXXXXX 100644 --- a/tests/qemucaps2xmloutdata/caps.riscv64.xml +++ b/tests/qemucaps2xmloutdata/caps.riscv64.xml @@ -XXX,XX +XXX,XX @@ <cpuselection/> <deviceboot/> <disksnapshot default='on' toggle='no'/> + <externalSnapshot/> </features> </guest> diff --git a/tests/qemucaps2xmloutdata/caps.s390x.xml b/tests/qemucaps2xmloutdata/caps.s390x.xml index XXXXXXX..XXXXXXX 100644 --- a/tests/qemucaps2xmloutdata/caps.s390x.xml +++ b/tests/qemucaps2xmloutdata/caps.s390x.xml @@ -XXX,XX +XXX,XX @@ <cpuselection/> <deviceboot/> <disksnapshot default='on' toggle='no'/> + <externalSnapshot/> </features> </guest> diff --git a/tests/qemucaps2xmloutdata/caps.sparc.xml b/tests/qemucaps2xmloutdata/caps.sparc.xml index XXXXXXX..XXXXXXX 100644 --- a/tests/qemucaps2xmloutdata/caps.sparc.xml +++ b/tests/qemucaps2xmloutdata/caps.sparc.xml @@ -XXX,XX +XXX,XX @@ <cpuselection/> <deviceboot/> <disksnapshot default='on' toggle='no'/> + <externalSnapshot/> </features> </guest> diff --git a/tests/qemucaps2xmloutdata/caps.x86_64+hvf.xml b/tests/qemucaps2xmloutdata/caps.x86_64+hvf.xml index XXXXXXX..XXXXXXX 100644 --- a/tests/qemucaps2xmloutdata/caps.x86_64+hvf.xml +++ b/tests/qemucaps2xmloutdata/caps.x86_64+hvf.xml @@ -XXX,XX +XXX,XX @@ <cpuselection/> <deviceboot/> <disksnapshot default='on' toggle='no'/> + <externalSnapshot/> </features> </guest> diff --git a/tests/qemucaps2xmloutdata/caps.x86_64.xml b/tests/qemucaps2xmloutdata/caps.x86_64.xml index XXXXXXX..XXXXXXX 100644 --- a/tests/qemucaps2xmloutdata/caps.x86_64.xml +++ b/tests/qemucaps2xmloutdata/caps.x86_64.xml @@ -XXX,XX +XXX,XX @@ <cpuselection/> <deviceboot/> <disksnapshot default='on' toggle='no'/> + <externalSnapshot/> </features> </guest> -- 2.41.0
Signed-off-by: Pavel Hrdina <phrdina@redhat.com> --- NEWS.rst | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/NEWS.rst b/NEWS.rst index XXXXXXX..XXXXXXX 100644 --- a/NEWS.rst +++ b/NEWS.rst @@ -XXX,XX +XXX,XX @@ v9.8.0 (unreleased) * **New features** + * QEMU: implement reverting external snapshots + + Reverting external snapshots is now possible using the existing API + ``virDomainSnapshotRevert()``. Management application can check host + capabilities for ``<externalSnapshot/>`` element within the list of + guest features to see if the current libvirt supports both deleting + and reverting external snapshots. + * **Improvements** * **Bug fixes** -- 2.41.0