[PATCH 0/5] hyperv: Implement basic snapshot query APIs
This is the first patch series adding snapshot support for the hyperv driver. It only implements the basic functions for querying information about snapshots and doesn't handle creating or deleting snapshots yet. Jonathon Jongsma (5): hyperv: Implement domainSnapshotLookupByName() hyperv: implement virDomainListAllSnapshots()/virDomainSnapshotNum() hyperv: implement virDomainSnapshotGetXMLDesc() hyperv: Implement virDomainSnapshotCurrent()/virDomainHasCurrentSnapshot() hyperv: Implement virDomainSnapshotGetParent() src/hyperv/hyperv_driver.c | 276 +++++++++++++++++++++++++++++++++++++ src/hyperv/hyperv_wmi.c | 20 +++ src/hyperv/hyperv_wmi.h | 7 + 3 files changed, 303 insertions(+) -- 2.53.0
Unfortunately Hyper-V does not enforce any uniqueness constraints on snapshot names (called ElementName in Hyper-V). So it's possible for multiple snapshots of the same domain to have identical ElementNames. Since libvirt uses the domain and snapshot name as a unique key to reference a snapshot, we can't use the hyperv ElementName as the snapshot name in libvirt. So instead I've decided to use the InstanceId of the snapshot as the snapshot name and use the ElementName as the snapshot description. This results in a worse user experience (since the snapshot names end up being something like "Microsoft:$(UUID)"), but guarantees that we will be able to uniquely reference every snapshot. Signed-off-by: Jonathon Jongsma <jjongsma@redhat.com> --- src/hyperv/hyperv_driver.c | 53 ++++++++++++++++++++++++++++++++++++++ src/hyperv/hyperv_wmi.h | 3 +++ 2 files changed, 56 insertions(+) diff --git a/src/hyperv/hyperv_driver.c b/src/hyperv/hyperv_driver.c index 906f6e5d19..44b671b45e 100644 --- a/src/hyperv/hyperv_driver.c +++ b/src/hyperv/hyperv_driver.c @@ -4077,6 +4077,58 @@ hypervDomainGetBlockInfo(virDomainPtr domain, } +static Msvm_VirtualSystemSettingData* +hypervDomainLookupSnapshotSD(virDomainPtr domain, const char *snapshot) +{ + hypervPrivate *priv = domain->conn->privateData; + g_autoptr(Msvm_VirtualSystemSettingData) vssd = NULL; + char domain_uuid_string[VIR_UUID_STRING_BUFLEN]; + g_auto(virBuffer) query = VIR_BUFFER_INITIALIZER; + + virUUIDFormat(domain->uuid, domain_uuid_string); + + /* Hyper-V does not enforce unique snapshot names per domain, so we don't + * use the Hyper-V snapshot's ElementName field as the libvirt snapshot name. + * Instead we use the unique InstanceID as the name, even though it is not as + * user-friendly */ + virBufferEscapeSQL(&query, + MSVM_VIRTUALSYSTEMSETTINGDATA_WQL_SELECT + "WHERE InstanceID='%s'", + snapshot); + virBufferEscapeSQL(&query, "AND VirtualSystemIdentifier='%s'", domain_uuid_string); + virBufferAddLit(&query, "AND VirtualSystemType='" + MSVM_VIRTUALSYSTEMSETTINGDATA_VIRTUALTYPE_SNAPSHOT "'"); + + if (hypervGetWmiClass(Msvm_VirtualSystemSettingData, &vssd) < 0) + return NULL; + + if (!vssd) { + virReportError(VIR_ERR_NO_DOMAIN_SNAPSHOT, + _("no domain snapshot with matching name '%1$s'"), snapshot); + return NULL; + } + + return g_steal_pointer(&vssd); +} + + +static virDomainSnapshotPtr +hypervDomainSnapshotLookupByName(virDomainPtr domain, + const char *name, + unsigned int flags) +{ + g_autoptr(Msvm_VirtualSystemSettingData) vssd = NULL; + + virCheckFlags(0, NULL); + + vssd = hypervDomainLookupSnapshotSD(domain, name); + if (vssd == NULL) + return NULL; + + return virGetDomainSnapshot(domain, name); +} + + static virHypervisorDriver hypervHypervisorDriver = { .name = "Hyper-V", .connectOpen = hypervConnectOpen, /* 0.9.5 */ @@ -4143,6 +4195,7 @@ static virHypervisorDriver hypervHypervisorDriver = { .connectIsAlive = hypervConnectIsAlive, /* 0.9.8 */ .domainInterfaceAddresses = hypervDomainInterfaceAddresses, /* 12.1.0 */ .domainGetBlockInfo = hypervDomainGetBlockInfo, /* 12.1.0 */ + .domainSnapshotLookupByName = hypervDomainSnapshotLookupByName, /* 12.2.0 */ }; diff --git a/src/hyperv/hyperv_wmi.h b/src/hyperv/hyperv_wmi.h index 65b1211b89..d577dbcecb 100644 --- a/src/hyperv/hyperv_wmi.h +++ b/src/hyperv/hyperv_wmi.h @@ -38,6 +38,9 @@ #define MSVM_IMAGEMANAGEMENTSERVICE_SELECTOR \ "CreationClassName=Msvm_ImageManagementService" +#define MSVM_VIRTUALSYSTEMSETTINGDATA_VIRTUALTYPE_SNAPSHOT \ + "Microsoft:Hyper-V:Snapshot:Realized" + int hypervVerifyResponse(WsManClient *client, WsXmlDocH response, const char *detail); -- 2.53.0
Signed-off-by: Jonathon Jongsma <jjongsma@redhat.com> --- src/hyperv/hyperv_driver.c | 82 ++++++++++++++++++++++++++++++++++++++ src/hyperv/hyperv_wmi.c | 20 ++++++++++ src/hyperv/hyperv_wmi.h | 4 ++ 3 files changed, 106 insertions(+) diff --git a/src/hyperv/hyperv_driver.c b/src/hyperv/hyperv_driver.c index 44b671b45e..8a1cc4b453 100644 --- a/src/hyperv/hyperv_driver.c +++ b/src/hyperv/hyperv_driver.c @@ -4129,6 +4129,86 @@ hypervDomainSnapshotLookupByName(virDomainPtr domain, } +static int +hypervDomainListAllSnapshots(virDomainPtr domain, + virDomainSnapshotPtr **snaps, + unsigned int flags) +{ + hypervPrivate *priv = domain->conn->privateData; + g_autoptr(Msvm_VirtualSystemSettingData) snapshots = NULL; + Msvm_VirtualSystemSettingData *snapshot = NULL; + g_autoptr(GList) filtered = NULL; + virDomainSnapshotPtr *snapshotsRet = NULL; + char uuid_string[VIR_UUID_STRING_BUFLEN]; + int count = 0; + int ret = -1; + + virCheckFlags(VIR_DOMAIN_SNAPSHOT_LIST_ROOTS | + VIR_DOMAIN_SNAPSHOT_LIST_METADATA | + VIR_DOMAIN_SNAPSHOT_LIST_NO_METADATA, -1); + + /* libvirt does not maintain any metadata for hyper-v snapshots */ + if (flags & VIR_DOMAIN_SNAPSHOT_LIST_METADATA) + return 0; + + virUUIDFormat(domain->uuid, uuid_string); + + if (hypervGetDomainSnapshotsSD(priv, uuid_string, &snapshots) < 0) + return -1; + + /* filter snapshots */ + for (snapshot = snapshots; snapshot; snapshot = snapshot->next) { + if ((flags & VIR_DOMAIN_SNAPSHOT_LIST_ROOTS) && snapshot->data->Parent) + continue; + + filtered = g_list_append(filtered, snapshot); + } + + count = g_list_length(filtered); + + if (!snaps) + return count; + + if (count > 0) { + GList *l = NULL; + int idx = 0; + + snapshotsRet = g_new0(virDomainSnapshotPtr, count); + + for (l = filtered; l; l = l->next) { + Msvm_VirtualSystemSettingData *vssd = l->data; + snapshotsRet[idx] = virGetDomainSnapshot(domain, vssd->data->InstanceID); + if (!snapshotsRet[idx]) + goto cleanup; + idx++; + } + } + + *snaps = snapshotsRet; + ret = count; + + cleanup: + if (ret < 0) { + size_t i; + for (i = 0; i < count; i++) { + if (snapshotsRet[i]) + virObjectUnref(snapshotsRet[i]); + } + VIR_FREE(snapshotsRet); + } + + return ret; +} + + +static int +hypervDomainSnapshotNum(virDomainPtr domain, + unsigned int flags) +{ + return hypervDomainListAllSnapshots(domain, NULL, flags); +} + + static virHypervisorDriver hypervHypervisorDriver = { .name = "Hyper-V", .connectOpen = hypervConnectOpen, /* 0.9.5 */ @@ -4196,6 +4276,8 @@ static virHypervisorDriver hypervHypervisorDriver = { .domainInterfaceAddresses = hypervDomainInterfaceAddresses, /* 12.1.0 */ .domainGetBlockInfo = hypervDomainGetBlockInfo, /* 12.1.0 */ .domainSnapshotLookupByName = hypervDomainSnapshotLookupByName, /* 12.2.0 */ + .domainListAllSnapshots = hypervDomainListAllSnapshots, /* 12.2.0 */ + .domainSnapshotNum = hypervDomainSnapshotNum, /* 12.2.0 */ }; diff --git a/src/hyperv/hyperv_wmi.c b/src/hyperv/hyperv_wmi.c index 7ae3afc40a..94a4789913 100644 --- a/src/hyperv/hyperv_wmi.c +++ b/src/hyperv/hyperv_wmi.c @@ -1406,6 +1406,26 @@ hypervGetMsvmVirtualSystemSettingDataFromUUID(hypervPrivate *priv, } +int +hypervGetDomainSnapshotsSD(hypervPrivate *priv, + const char *domain_uuid_string, + Msvm_VirtualSystemSettingData **list) +{ + g_auto(virBuffer) query = VIR_BUFFER_INITIALIZER; + + virBufferAsprintf(&query, + "ASSOCIATORS OF {Msvm_ComputerSystem.CreationClassName='Msvm_ComputerSystem',Name='%s'} " + "WHERE AssocClass = Msvm_SnapshotOfVirtualSystem " + "ResultClass = Msvm_VirtualSystemSettingData", + domain_uuid_string); + + if (hypervGetWmiClass(Msvm_VirtualSystemSettingData, list) < 0) + return -1; + + return 0; +} + + #define hypervGetSettingData(type, id, out) \ g_auto(virBuffer) query = VIR_BUFFER_INITIALIZER; \ virBufferEscapeSQL(&query, \ diff --git a/src/hyperv/hyperv_wmi.h b/src/hyperv/hyperv_wmi.h index d577dbcecb..134d8894e6 100644 --- a/src/hyperv/hyperv_wmi.h +++ b/src/hyperv/hyperv_wmi.h @@ -240,6 +240,10 @@ int hypervGetMsvmVirtualSystemSettingDataFromUUID(hypervPrivate *priv, const char *uuid_string, Msvm_VirtualSystemSettingData **list); +int hypervGetDomainSnapshotsSD(hypervPrivate *priv, + const char *domain_uuid_string, + Msvm_VirtualSystemSettingData **list); + int hypervGetResourceAllocationSD(hypervPrivate *priv, const char *id, Msvm_ResourceAllocationSettingData **data); -- 2.53.0
This function is required for `virsh snapshot-list` to work. Signed-off-by: Jonathon Jongsma <jjongsma@redhat.com> --- src/hyperv/hyperv_driver.c | 67 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 67 insertions(+) diff --git a/src/hyperv/hyperv_driver.c b/src/hyperv/hyperv_driver.c index 8a1cc4b453..4a4db0e76b 100644 --- a/src/hyperv/hyperv_driver.c +++ b/src/hyperv/hyperv_driver.c @@ -41,6 +41,7 @@ #include "virstring.h" #include "virkeycode.h" #include "domain_conf.h" +#include "snapshot_conf.h" #include "virfdstream.h" #include "virfile.h" @@ -4209,6 +4210,71 @@ hypervDomainSnapshotNum(virDomainPtr domain, } +/* A snapshot's parent is specified as an object path like 'InstanceID="$ID"'. + * This function extracts the id portion. */ +static char * +hypervParseInstanceIdFromParentPath(const char *obj_path) +{ + const char *const instance_prefix = "InstanceID=\""; + const char *id_start = NULL; + if (!obj_path) + return NULL; + + id_start = strstr(obj_path, instance_prefix); + if (id_start) { + const char *id_end; + id_start += strlen(instance_prefix); + id_end = strchr(id_start, '"'); + if (id_end) { + g_autofree char* parent_id_escaped = g_strndup(id_start, id_end - id_start); + char* parent_id = virStringReplace(parent_id_escaped, "\\\\", "\\"); + return parent_id; + } + } + return NULL; +} + + +static char * +hypervDomainSnapshotGetXMLDesc(virDomainSnapshotPtr snapshot, + unsigned int flags) +{ + hypervPrivate *priv = snapshot->domain->conn->privateData; + g_autoptr(Msvm_VirtualSystemSettingData) vssd = NULL; + g_autoptr(virDomainSnapshotDef) def = NULL; + char uuidstr[VIR_UUID_STRING_BUFLEN]; + + virCheckFlags(VIR_DOMAIN_SNAPSHOT_XML_SECURE, NULL); + + vssd = hypervDomainLookupSnapshotSD(snapshot->domain, snapshot->name); + if (!vssd) + return NULL; + + if (!(def = virDomainSnapshotDefNew())) + return NULL; + + def->parent.name = g_strdup(vssd->data->InstanceID); + def->parent.description = g_strdup(vssd->data->ElementName); + def->parent.parent_name = hypervParseInstanceIdFromParentPath(vssd->data->Parent); + /* IsSaved indicates the snapshot configuration references a saved memory + * state file, meaning the snapshot was taken from a running VM and includes + * full machine state. Otherwise it's either a 'production' checkpoint or an + * offline snapshot, which are both disk-only. */ + def->state = vssd->data->IsSaved ? VIR_DOMAIN_SNAPSHOT_RUNNING : VIR_DOMAIN_SNAPSHOT_DISK_SNAPSHOT; + + if (vssd->data->CreationTime) { + g_autoptr(GDateTime) dt = g_date_time_new_from_iso8601(vssd->data->CreationTime, NULL); + if (dt) + def->parent.creationTime = g_date_time_to_unix(dt); + } + + virUUIDFormat(snapshot->domain->uuid, uuidstr); + + return virDomainSnapshotDefFormat(uuidstr, def, priv->xmlopt, + virDomainSnapshotFormatConvertXMLFlags(flags)); +} + + static virHypervisorDriver hypervHypervisorDriver = { .name = "Hyper-V", .connectOpen = hypervConnectOpen, /* 0.9.5 */ @@ -4278,6 +4344,7 @@ static virHypervisorDriver hypervHypervisorDriver = { .domainSnapshotLookupByName = hypervDomainSnapshotLookupByName, /* 12.2.0 */ .domainListAllSnapshots = hypervDomainListAllSnapshots, /* 12.2.0 */ .domainSnapshotNum = hypervDomainSnapshotNum, /* 12.2.0 */ + .domainSnapshotGetXMLDesc = hypervDomainSnapshotGetXMLDesc, /* 12.2.0 */ }; -- 2.53.0
Signed-off-by: Jonathon Jongsma <jjongsma@redhat.com> --- src/hyperv/hyperv_driver.c | 48 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) diff --git a/src/hyperv/hyperv_driver.c b/src/hyperv/hyperv_driver.c index 4a4db0e76b..dd1248ced1 100644 --- a/src/hyperv/hyperv_driver.c +++ b/src/hyperv/hyperv_driver.c @@ -4235,6 +4235,52 @@ hypervParseInstanceIdFromParentPath(const char *obj_path) } +static int +hypervDomainHasCurrentSnapshot(virDomainPtr domain, + unsigned int flags) +{ + g_autoptr(Msvm_VirtualSystemSettingData) vssd = NULL; + hypervPrivate *priv = domain->conn->privateData; + char uuid_string[VIR_UUID_STRING_BUFLEN]; + + virCheckFlags(0, -1); + + virUUIDFormat(domain->uuid, uuid_string); + + if (hypervGetMsvmVirtualSystemSettingDataFromUUID(priv, uuid_string, &vssd) < 0) + return -1; + + return (vssd->data->Parent != NULL); +} + + +static virDomainSnapshotPtr +hypervDomainSnapshotCurrent(virDomainPtr domain, + unsigned int flags) +{ + hypervPrivate *priv = domain->conn->privateData; + char uuid_string[VIR_UUID_STRING_BUFLEN]; + g_autoptr(Msvm_VirtualSystemSettingData) vssd = NULL; + g_autofree char* current_snapshot = NULL; + + virCheckFlags(0, NULL); + + virUUIDFormat(domain->uuid, uuid_string); + + if (hypervGetMsvmVirtualSystemSettingDataFromUUID(priv, uuid_string, &vssd) < 0) + return NULL; + + current_snapshot = hypervParseInstanceIdFromParentPath(vssd->data->Parent); + if (!current_snapshot) { + virReportError(VIR_ERR_NO_DOMAIN_SNAPSHOT, "%s", + _("the domain does not have a current snapshot")); + return NULL; + } + + return virGetDomainSnapshot(domain, current_snapshot); +} + + static char * hypervDomainSnapshotGetXMLDesc(virDomainSnapshotPtr snapshot, unsigned int flags) @@ -4345,6 +4391,8 @@ static virHypervisorDriver hypervHypervisorDriver = { .domainListAllSnapshots = hypervDomainListAllSnapshots, /* 12.2.0 */ .domainSnapshotNum = hypervDomainSnapshotNum, /* 12.2.0 */ .domainSnapshotGetXMLDesc = hypervDomainSnapshotGetXMLDesc, /* 12.2.0 */ + .domainHasCurrentSnapshot = hypervDomainHasCurrentSnapshot, /* 12.2.0 */ + .domainSnapshotCurrent = hypervDomainSnapshotCurrent, /* 12.2.0 */ }; -- 2.53.0
On 3/6/26 23:12, Jonathon Jongsma via Devel wrote:
Signed-off-by: Jonathon Jongsma <jjongsma@redhat.com> --- src/hyperv/hyperv_driver.c | 48 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+)
diff --git a/src/hyperv/hyperv_driver.c b/src/hyperv/hyperv_driver.c index 4a4db0e76b..dd1248ced1 100644 --- a/src/hyperv/hyperv_driver.c +++ b/src/hyperv/hyperv_driver.c @@ -4235,6 +4235,52 @@ hypervParseInstanceIdFromParentPath(const char *obj_path) }
+static int +hypervDomainHasCurrentSnapshot(virDomainPtr domain, + unsigned int flags) +{ + g_autoptr(Msvm_VirtualSystemSettingData) vssd = NULL; + hypervPrivate *priv = domain->conn->privateData; + char uuid_string[VIR_UUID_STRING_BUFLEN]; + + virCheckFlags(0, -1); + + virUUIDFormat(domain->uuid, uuid_string); + + if (hypervGetMsvmVirtualSystemSettingDataFromUUID(priv, uuid_string, &vssd) < 0) + return -1; + + return (vssd->data->Parent != NULL);
Drop the parentheses. Michal
Signed-off-by: Jonathon Jongsma <jjongsma@redhat.com> --- src/hyperv/hyperv_driver.c | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/src/hyperv/hyperv_driver.c b/src/hyperv/hyperv_driver.c index dd1248ced1..d3e17d7a16 100644 --- a/src/hyperv/hyperv_driver.c +++ b/src/hyperv/hyperv_driver.c @@ -4321,6 +4321,31 @@ hypervDomainSnapshotGetXMLDesc(virDomainSnapshotPtr snapshot, } +static virDomainSnapshotPtr +hypervDomainSnapshotGetParent(virDomainSnapshotPtr snapshot, + unsigned int flags) +{ + g_autoptr(Msvm_VirtualSystemSettingData) vssd = NULL; + g_autofree char* parent_id = NULL; + + virCheckFlags(0, NULL); + + vssd = hypervDomainLookupSnapshotSD(snapshot->domain, snapshot->name); + if (!vssd) + return NULL; + + parent_id = hypervParseInstanceIdFromParentPath(vssd->data->Parent); + if (!parent_id) { + virReportError(VIR_ERR_NO_DOMAIN_SNAPSHOT, + _("snapshot '%1$s' does not have a parent"), + snapshot->name); + return NULL; + } + + return virGetDomainSnapshot(snapshot->domain, parent_id); +} + + static virHypervisorDriver hypervHypervisorDriver = { .name = "Hyper-V", .connectOpen = hypervConnectOpen, /* 0.9.5 */ @@ -4393,6 +4418,7 @@ static virHypervisorDriver hypervHypervisorDriver = { .domainSnapshotGetXMLDesc = hypervDomainSnapshotGetXMLDesc, /* 12.2.0 */ .domainHasCurrentSnapshot = hypervDomainHasCurrentSnapshot, /* 12.2.0 */ .domainSnapshotCurrent = hypervDomainSnapshotCurrent, /* 12.2.0 */ + .domainSnapshotGetParent = hypervDomainSnapshotGetParent, /* 12.2.0 */ }; -- 2.53.0
On 3/6/26 23:12, Jonathon Jongsma via Devel wrote:
This is the first patch series adding snapshot support for the hyperv driver. It only implements the basic functions for querying information about snapshots and doesn't handle creating or deleting snapshots yet.
Jonathon Jongsma (5): hyperv: Implement domainSnapshotLookupByName() hyperv: implement virDomainListAllSnapshots()/virDomainSnapshotNum() hyperv: implement virDomainSnapshotGetXMLDesc() hyperv: Implement virDomainSnapshotCurrent()/virDomainHasCurrentSnapshot() hyperv: Implement virDomainSnapshotGetParent()
src/hyperv/hyperv_driver.c | 276 +++++++++++++++++++++++++++++++++++++ src/hyperv/hyperv_wmi.c | 20 +++ src/hyperv/hyperv_wmi.h | 7 + 3 files changed, 303 insertions(+)
Reviewed-by: Michal Privoznik <mprivozn@redhat.com> Michal
participants (2)
-
Jonathon Jongsma -
Michal Prívozník