From nobody Tue Oct 22 22:31:12 2024 Delivered-To: importer@patchew.org Received-SPF: pass (zohomail.com: domain of redhat.com designates 170.10.129.124 as permitted sender) client-ip=170.10.129.124; envelope-from=libvir-list-bounces@redhat.com; helo=us-smtp-delivery-124.mimecast.com; Authentication-Results: mx.zohomail.com; dkim=pass; spf=pass (zohomail.com: domain of redhat.com designates 170.10.129.124 as permitted sender) smtp.mailfrom=libvir-list-bounces@redhat.com; dmarc=pass(p=none dis=none) header.from=redhat.com ARC-Seal: i=1; a=rsa-sha256; t=1692005836; cv=none; d=zohomail.com; s=zohoarc; b=HEPOp2fNhsptySmo2glf+Z2jWnBqjYO3Ig9/zFXhA1tChBi8tHuhysufQUGl7DM0xCFtLqTeszAiNJh/3u0kp7atvoRVoepXBenyKDzphkFIjMj0X1DdP628q0r8Sx4dmtz2voUkriUPKo3Wh9hjKBNym54iKloRpts8WvgOM3k= ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=zohomail.com; s=zohoarc; t=1692005836; h=Content-Type:Content-Transfer-Encoding:Date:From:In-Reply-To:List-Subscribe:List-Post:List-Id:List-Archive:List-Help:List-Unsubscribe:MIME-Version:Message-ID:References:Sender:Subject:To; bh=973AmwVShniIr87hBqayoN6GAFp8q8TpMvtgGUoig5o=; b=HXMY/+RJSqlt+GZj2fIjefS/ibQtEhmXPo6olROuuYNgqrWgsuPnE/KdsRFKVf8TL6eZUIqjWemCA8W8qzxdspeHu7dW3Dtiy2PKkMzG6OgHRWUumqC0nkZt2bodDlJZ/speyIKPxGwMjj2V9P+/+RIzYX8XV0zMp+bS8noeUgg= ARC-Authentication-Results: i=1; mx.zohomail.com; dkim=pass; spf=pass (zohomail.com: domain of redhat.com designates 170.10.129.124 as permitted sender) smtp.mailfrom=libvir-list-bounces@redhat.com; dmarc=pass header.from= (p=none dis=none) Return-Path: Received: from us-smtp-delivery-124.mimecast.com (us-smtp-delivery-124.mimecast.com [170.10.129.124]) by mx.zohomail.com with SMTPS id 1692005836458339.3371922700653; Mon, 14 Aug 2023 02:37:16 -0700 (PDT) Received: from mimecast-mx02.redhat.com (66.187.233.73 [66.187.233.73]) by relay.mimecast.com with ESMTP with STARTTLS (version=TLSv1.2, cipher=TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384) id us-mta-231-FQcjKOj1PsqTWrxOI245Qg-1; Mon, 14 Aug 2023 05:37:06 -0400 Received: from smtp.corp.redhat.com (int-mx10.intmail.prod.int.rdu2.redhat.com [10.11.54.10]) (using TLSv1.2 with cipher AECDH-AES256-SHA (256/256 bits)) (No client certificate requested) by mimecast-mx02.redhat.com (Postfix) with ESMTPS id 5969E3814950; Mon, 14 Aug 2023 09:37:04 +0000 (UTC) Received: from mm-prod-listman-01.mail-001.prod.us-east-1.aws.redhat.com (mm-prod-listman-01.mail-001.prod.us-east-1.aws.redhat.com [10.30.29.100]) by smtp.corp.redhat.com (Postfix) with ESMTP id 40891403166; Mon, 14 Aug 2023 09:37:04 +0000 (UTC) Received: from mm-prod-listman-01.mail-001.prod.us-east-1.aws.redhat.com (localhost [IPv6:::1]) by mm-prod-listman-01.mail-001.prod.us-east-1.aws.redhat.com (Postfix) with ESMTP id A2A9619459EC; Mon, 14 Aug 2023 09:36:55 +0000 (UTC) Received: from smtp.corp.redhat.com (int-mx01.intmail.prod.int.rdu2.redhat.com [10.11.54.1]) by mm-prod-listman-01.mail-001.prod.us-east-1.aws.redhat.com (Postfix) with ESMTP id A5017194658C for ; Mon, 14 Aug 2023 09:36:37 +0000 (UTC) Received: by smtp.corp.redhat.com (Postfix) id F39B140C207B; Mon, 14 Aug 2023 09:36:25 +0000 (UTC) Received: from antique-work.redhat.com (unknown [10.45.226.4]) by smtp.corp.redhat.com (Postfix) with ESMTP id 959A240C2073 for ; Mon, 14 Aug 2023 09:36:25 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com; s=mimecast20190719; t=1692005835; h=from:from:sender:sender:reply-to:subject:subject:date:date: message-id:message-id:to:to:cc:mime-version:mime-version: content-type:content-type: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references:list-id:list-help: list-unsubscribe:list-subscribe:list-post; bh=973AmwVShniIr87hBqayoN6GAFp8q8TpMvtgGUoig5o=; b=KSf2tDc9rlSSi8w+PEqqRd9Z+NKuYVGfFfMZvoSaoLClpLt2b1E2+mflb/ILQ7XJfReDT3 TXqkRWqTw/qPN9JydsiVA0RhcU4G7uEenQLVL2VT5iDbWl+tmPlzFzGYzUh2/0ZJ7jdoJ0 T6z5O3FPGOHQLHKapPPyHhLY52xYAdU= X-MC-Unique: FQcjKOj1PsqTWrxOI245Qg-1 X-Original-To: libvir-list@listman.corp.redhat.com From: Pavel Hrdina To: libvir-list@redhat.com Subject: [libvirt PATCH v3 12/25] qemu_snapshot: introduce external snapshot revert support Date: Mon, 14 Aug 2023 11:36:04 +0200 Message-ID: <5fb760599eff3728797165aec65a29178349993e.1692005543.git.phrdina@redhat.com> In-Reply-To: References: MIME-Version: 1.0 X-Scanned-By: MIMEDefang 3.1 on 10.11.54.1 X-BeenThere: libvir-list@redhat.com X-Mailman-Version: 2.1.29 Precedence: list List-Id: Development discussions about the libvirt library & tools List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: libvir-list-bounces@redhat.com Sender: "libvir-list" X-Scanned-By: MIMEDefang 3.1 on 10.11.54.10 X-Mimecast-Spam-Score: 0 X-Mimecast-Originator: redhat.com Content-Transfer-Encoding: quoted-printable X-ZohoMail-DKIM: pass (identity @redhat.com) X-ZM-MESSAGEID: 1692005838154100009 Content-Type: text/plain; charset="utf-8"; x-default="true" When reverting to external snapshot we need to create new overlay qcow2 files from the disk files the VM had when the snapshot was taken. There are some specifics and limitations when reverting to a snapshot: 1) When reverting to last snapshot we need to first create new overlay files before we can safely delete the old overlay files in case the creation fails so we have still recovery option when we error out. These new files will not have the suffix as when the snapshot was created as renaming the original files in order to use the same file names as when the snapshot was created would add unnecessary complexity to the code. 2) When reverting to any snapshot we will always create overlay files for every disk the VM had when the snapshot was done. Otherwise we would have to figure out if there is any other qcow2 image already using any of the VM disks as backing store and that itself might be extremely complex and in some cases impossible. 3) When reverting from any state the current overlay files will be always removed as that VM state is not meant to be saved. It's the same as with internal snapshots. If user want's to keep the current state before reverting they need to create a new snapshot. For now this will only work if the current snapshot is the last. Signed-off-by: Pavel Hrdina Reviewed-by: Peter Krempa --- src/qemu/qemu_snapshot.c | 278 ++++++++++++++++++++++++++++++++++++++- 1 file changed, 274 insertions(+), 4 deletions(-) diff --git a/src/qemu/qemu_snapshot.c b/src/qemu/qemu_snapshot.c index 82fbb79ab0..0534989e37 100644 --- a/src/qemu/qemu_snapshot.c +++ b/src/qemu/qemu_snapshot.c @@ -18,6 +18,8 @@ =20 #include =20 +#include + #include "qemu_snapshot.h" =20 #include "qemu_monitor.h" @@ -1982,6 +1984,229 @@ qemuSnapshotRevertWriteMetadata(virDomainObj *vm, } =20 =20 +/** + * qemuSnapshotRevertExternalPrepare: + * @vm: domain object + * @tmpsnapdef: temporary snapshot definition + * @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 + * + * 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. + * + * Returns 0 in success, -1 on error. + */ +static int +qemuSnapshotRevertExternalPrepare(virDomainObj *vm, + virDomainSnapshotDef *tmpsnapdef, + virDomainMomentObj *snap, + virDomainDef *config, + virDomainDef *inactiveConfig, + int *memsnapFD, + char **memsnapPath) +{ + size_t i; + bool active =3D virDomainObjIsActive(vm); + virDomainDef *domdef =3D NULL; + virDomainSnapshotDef *snapdef =3D virDomainSnapshotObjGetDef(snap); + + if (config) { + domdef =3D config; + } else { + domdef =3D inactiveConfig; + } + + /* We need this to generate creation timestamp that is used as default + * snapshot name. */ + if (virDomainMomentDefPostParse(&tmpsnapdef->parent) < 0) + return -1; + + if (virDomainSnapshotAlignDisks(tmpsnapdef, domdef, + VIR_DOMAIN_SNAPSHOT_LOCATION_EXTERNAL, + false, true) < 0) { + return -1; + } + + for (i =3D 0; i < tmpsnapdef->ndisks; i++) { + virDomainSnapshotDiskDef *snapdisk =3D &tmpsnapdef->disks[i]; + virDomainDiskDef *domdisk =3D domdef->disks[i]; + + if (qemuSnapshotPrepareDiskExternal(domdisk, snapdisk, active, fal= se) < 0) + return -1; + } + + if (memsnapFD && memsnapPath && snapdef->memorysnapshotfile) { + virQEMUDriver *driver =3D ((qemuDomainObjPrivate *) vm->privateDat= a)->driver; + g_autoptr(virQEMUDriverConfig) cfg =3D virQEMUDriverGetConfig(driv= er); + + *memsnapPath =3D snapdef->memorysnapshotfile; + *memsnapFD =3D qemuDomainOpenFile(cfg, NULL, *memsnapPath, O_RDONL= Y, NULL); + } + + return 0; +} + + +/** + * qemuSnapshotRevertExternalActive: + * @vm: domain object + * @tmpsnapdef: temporary snapshot definition + * + * Creates a new disk overlays using the temporary snapshot + * definition @tmpsnapdef for running VM by calling QMP APIs. + * + * Returns 0 on success, -1 on error. + */ +static int +qemuSnapshotRevertExternalActive(virDomainObj *vm, + virDomainSnapshotDef *tmpsnapdef) +{ + size_t i; + g_autoptr(GHashTable) blockNamedNodeData =3D NULL; + g_autoptr(qemuSnapshotDiskContext) snapctxt =3D NULL; + + snapctxt =3D qemuSnapshotDiskContextNew(tmpsnapdef->ndisks, vm, VIR_AS= YNC_JOB_SNAPSHOT); + + if (!(blockNamedNodeData =3D qemuBlockGetNamedNodeData(vm, VIR_ASYNC_J= OB_SNAPSHOT))) + return -1; + + for (i =3D 0; i < tmpsnapdef->ndisks; i++) { + if (qemuSnapshotDiskPrepareOne(snapctxt, + vm->def->disks[i], + tmpsnapdef->disks + i, + blockNamedNodeData, + false, + true) < 0) + return -1; + } + + if (qemuSnapshotDiskCreate(snapctxt) < 0) + return -1; + + return 0; +} + + +/** + * qemuSnapshotRevertExternalInactive: + * @vm: domain object + * @tmpsnapdef: temporary snapshot definition + * @domdef: offline domain definition + * + * Creates a new disk overlays using the temporary snapshot + * definition @tmpsnapdef for offline VM by calling qemu-img. + * + * Returns 0 on success, -1 on error. + */ +static int +qemuSnapshotRevertExternalInactive(virDomainObj *vm, + virDomainSnapshotDef *tmpsnapdef, + virDomainDef *domdef) +{ + virQEMUDriver *driver =3D QEMU_DOMAIN_PRIVATE(vm)->driver; + g_autoptr(virBitmap) created =3D NULL; + + created =3D virBitmapNew(tmpsnapdef->ndisks); + + if (qemuSnapshotDomainDefUpdateDisk(domdef, tmpsnapdef, false) < 0) + return -1; + + if (qemuSnapshotCreateQcow2Files(driver, domdef, tmpsnapdef, created) = < 0) { + ssize_t bit =3D -1; + virErrorPtr err =3D NULL; + + virErrorPreserveLast(&err); + + while ((bit =3D virBitmapNextSetBit(created, bit)) >=3D 0) { + virDomainSnapshotDiskDef *snapdisk =3D &(tmpsnapdef->disks[bit= ]); + + if (virStorageSourceInit(snapdisk->src) < 0 || + virStorageSourceUnlink(snapdisk->src) < 0) { + VIR_WARN("Failed to remove snapshot image '%s'", + snapdisk->src->path); + } + } + + virErrorRestore(&err); + + return -1; + } + + return 0; +} + + +/** + * qemuSnapshotRevertExternalFinish: + * @vm: domain object + * @tmpsnapdef: temporary snapshot definition + * @snap: snapshot object we are reverting to + * + * Finishes disk overlay creation by removing existing overlays that + * will no longer be used if there are any and updating snapshot @snap + * metadata and current snapshot metadata so it can be saved once the + * revert is completed. + */ +static void +qemuSnapshotRevertExternalFinish(virDomainObj *vm, + virDomainSnapshotDef *tmpsnapdef, + virDomainMomentObj *snap) +{ + size_t i; + virDomainMomentObj *curSnap =3D virDomainSnapshotGetCurrent(vm->snapsh= ots); + virDomainSnapshotDef *curdef =3D virDomainSnapshotObjGetDef(curSnap); + virDomainSnapshotDef *snapdef =3D virDomainSnapshotObjGetDef(snap); + + if (curdef->revertdisks) { + for (i =3D 0; i < curdef->nrevertdisks; i++) { + virDomainSnapshotDiskDef *snapdisk =3D &(curdef->revertdisks[i= ]); + + if (virStorageSourceInit(snapdisk->src) < 0 || + virStorageSourceUnlink(snapdisk->src) < 0) { + VIR_WARN("Failed to remove snapshot image '%s'", + snapdisk->src->path); + } + + virDomainSnapshotDiskDefClear(snapdisk); + } + + g_clear_pointer(&curdef->revertdisks, g_free); + curdef->nrevertdisks =3D 0; + } else { + for (i =3D 0; i < curdef->ndisks; i++) { + virDomainSnapshotDiskDef *snapdisk =3D &(curdef->disks[i]); + + if (virStorageSourceInit(snapdisk->src) < 0 || + virStorageSourceUnlink(snapdisk->src) < 0) { + VIR_WARN("Failed to remove snapshot image '%s'", + snapdisk->src->path); + } + } + } + + if (snap->nchildren !=3D 0) { + snapdef->revertdisks =3D g_steal_pointer(&tmpsnapdef->disks); + snapdef->nrevertdisks =3D tmpsnapdef->ndisks; + tmpsnapdef->ndisks =3D 0; + } else { + for (i =3D 0; i < snapdef->ndisks; i++) { + virDomainSnapshotDiskDefClear(&snapdef->disks[i]); + } + g_free(snapdef->disks); + snapdef->disks =3D g_steal_pointer(&tmpsnapdef->disks); + snapdef->ndisks =3D tmpsnapdef->ndisks; + tmpsnapdef->ndisks =3D 0; + } +} + + static int qemuSnapshotRevertActive(virDomainObj *vm, virDomainSnapshotPtr snapshot, @@ -1996,10 +2221,14 @@ qemuSnapshotRevertActive(virDomainObj *vm, { virObjectEvent *event =3D NULL; virObjectEvent *event2 =3D NULL; + virDomainMomentObj *loadSnap =3D NULL; + VIR_AUTOCLOSE memsnapFD =3D -1; + char *memsnapPath =3D NULL; int detail; bool defined =3D false; qemuDomainSaveCookie *cookie =3D (qemuDomainSaveCookie *) snapdef->coo= kie; int rc; + g_autoptr(virDomainSnapshotDef) tmpsnapdef =3D NULL; =20 start_flags |=3D VIR_QEMU_PROCESS_START_PAUSED; =20 @@ -2017,6 +2246,19 @@ qemuSnapshotRevertActive(virDomainObj *vm, virObjectEventStateQueue(driver->domainEventState, event); } =20 + if (virDomainSnapshotIsExternal(snap)) { + if (!(tmpsnapdef =3D virDomainSnapshotDefNew())) + return -1; + + if (qemuSnapshotRevertExternalPrepare(vm, tmpsnapdef, snap, + *config, *inactiveConfig, + &memsnapFD, &memsnapPath) < = 0) { + return -1; + } + } else { + loadSnap =3D snap; + } + if (*inactiveConfig) { virDomainObjAssignDef(vm, inactiveConfig, false, NULL); defined =3D true; @@ -2033,7 +2275,8 @@ qemuSnapshotRevertActive(virDomainObj *vm, =20 rc =3D qemuProcessStart(snapshot->domain->conn, driver, vm, cookie ? cookie->cpu : NULL, - VIR_ASYNC_JOB_SNAPSHOT, NULL, -1, NULL, snap, + VIR_ASYNC_JOB_SNAPSHOT, NULL, memsnapFD, + memsnapPath, loadSnap, VIR_NETDEV_VPORT_PROFILE_OP_CREATE, start_flags); virDomainAuditStart(vm, "from-snapshot", rc >=3D 0); @@ -2045,6 +2288,14 @@ qemuSnapshotRevertActive(virDomainObj *vm, if (rc < 0) return -1; =20 + + if (virDomainSnapshotIsExternal(snap)) { + if (qemuSnapshotRevertExternalActive(vm, tmpsnapdef) < 0) + return -1; + + qemuSnapshotRevertExternalFinish(vm, tmpsnapdef, snap); + } + /* Touch up domain state. */ if (!(flags & VIR_DOMAIN_SNAPSHOT_REVERT_RUNNING) && (snapdef->state =3D=3D VIR_DOMAIN_SNAPSHOT_PAUSED || @@ -2119,6 +2370,7 @@ qemuSnapshotRevertInactive(virDomainObj *vm, int detail; bool defined =3D false; int rc; + g_autoptr(virDomainSnapshotDef) tmpsnapdef =3D NULL; =20 /* Transitions 1, 4, 7 */ /* Newer qemu -loadvm refuses to revert to the state of a snapshot @@ -2138,9 +2390,27 @@ qemuSnapshotRevertInactive(virDomainObj *vm, virObjectEventStateQueue(driver->domainEventState, event); } =20 - if (qemuSnapshotInternalRevertInactive(driver, vm, snap) < 0) { - qemuDomainRemoveInactive(driver, vm, 0, false); - return -1; + if (virDomainSnapshotIsExternal(snap)) { + if (!(tmpsnapdef =3D virDomainSnapshotDefNew())) + return -1; + + if (qemuSnapshotRevertExternalPrepare(vm, tmpsnapdef, snap, + NULL, *inactiveConfig, + NULL, NULL) < 0) { + return -1; + } + + if (qemuSnapshotRevertExternalInactive(vm, tmpsnapdef, + *inactiveConfig) < 0) { + return -1; + } + + qemuSnapshotRevertExternalFinish(vm, tmpsnapdef, snap); + } else { + if (qemuSnapshotInternalRevertInactive(driver, vm, snap) < 0) { + qemuDomainRemoveInactive(driver, vm, 0, false); + return -1; + } } =20 if (*inactiveConfig) { --=20 2.41.0