Add ability to create and delete basic snapshots in thed hyperv driver. Changes in v2: - re-word error message about configuring disks / memory for snapshots - split out the utility function for extracting async method output params Jonathon Jongsma (4): hyperv: Add snapshot related WMI class definitions hyperv: Implement domainSnapshotDelete() hyperv: Add a utility function for getting method output params hyperv: Implement domainSnapshotCreateXML() src/hyperv/hyperv_driver.c | 129 ++++++++++++++++++++++++ src/hyperv/hyperv_wmi.c | 139 ++++++++++++++++++++++++++ src/hyperv/hyperv_wmi.h | 17 ++++ src/hyperv/hyperv_wmi_generator.input | 42 ++++++++ 4 files changed, 327 insertions(+) -- 2.53.0
Msvm_VirtualSystemSnapshotService and Msvm_VirtualSystemSnapshotSettingData --- src/hyperv/hyperv_wmi.h | 11 +++++++ src/hyperv/hyperv_wmi_generator.input | 42 +++++++++++++++++++++++++++ 2 files changed, 53 insertions(+) diff --git a/src/hyperv/hyperv_wmi.h b/src/hyperv/hyperv_wmi.h index 977245bed5..0560f83e0d 100644 --- a/src/hyperv/hyperv_wmi.h +++ b/src/hyperv/hyperv_wmi.h @@ -41,6 +41,17 @@ #define MSVM_VIRTUALSYSTEMSETTINGDATA_VIRTUALTYPE_SNAPSHOT \ "Microsoft:Hyper-V:Snapshot:Realized" +#define MSVM_VIRTUALSYSTEMSNAPSHOTSERVICE_SELECTOR \ + "CreationClassName=Msvm_VirtualSystemSnapshotService" + +/* Msvm_VirtualSystemSnapshotSettingData ConsistencyLevel values */ +#define HYPERV_SNAPSHOT_CONSISTENCY_APP_CONSISTENT "1" /* VSS (production checkpoint) */ +#define HYPERV_SNAPSHOT_CONSISTENCY_CRASH_CONSISTENT "2" /* Standard checkpoint */ + +/* Msvm_VirtualSystemSnapshotService CreateSnapshot SnapshotType values */ +#define HYPERV_SNAPSHOT_TYPE_FULL "2" /* Full snapshot including memory if running */ +#define HYPERV_SNAPSHOT_TYPE_DISK_ONLY "3" /* Disk-only, no memory state */ + int hypervVerifyResponse(WsManClient *client, WsXmlDocH response, const char *detail); diff --git a/src/hyperv/hyperv_wmi_generator.input b/src/hyperv/hyperv_wmi_generator.input index b3cd9d19fb..4ba69aa77a 100644 --- a/src/hyperv/hyperv_wmi_generator.input +++ b/src/hyperv/hyperv_wmi_generator.input @@ -1261,3 +1261,45 @@ class Msvm_SecuritySettingData boolean EncryptStateAndVmMigrationTraffic boolean VirtualizationBasedSecurityOptOut end + +class Msvm_VirtualSystemSnapshotService + string InstanceID + string Caption + string Description + string ElementName + datetime InstallDate + string Name + uint16 OperationalStatus[] + string StatusDescriptions[] + string Status + uint16 HealthState + uint16 CommunicationStatus + uint16 DetailedStatus + uint16 OperatingStatus + uint16 PrimaryStatus + uint16 EnabledState + string OtherEnabledState + uint16 RequestedState + uint16 EnabledDefault + datetime TimeOfLastStateChange + uint16 AvailableRequestedStates[] + uint16 TransitioningToState + string SystemCreationClassName + string SystemName + string CreationClassName + string PrimaryOwnerName + string PrimaryOwnerContact + string StartMode + boolean Started +end + + +class Msvm_VirtualSystemSnapshotSettingData + string InstanceID + string Caption + string Description + string ElementName + uint8 ConsistencyLevel + boolean IgnoreNonSnapshottableDisks + uint8 GuestBackupType +end -- 2.53.0
Signed-off-by: Jonathon Jongsma <jjongsma@redhat.com> --- src/hyperv/hyperv_driver.c | 41 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/src/hyperv/hyperv_driver.c b/src/hyperv/hyperv_driver.c index 18e06f0e2a..841d9ccaa5 100644 --- a/src/hyperv/hyperv_driver.c +++ b/src/hyperv/hyperv_driver.c @@ -4163,6 +4163,46 @@ hypervDomainSnapshotLookupByName(virDomainPtr domain, } +static int +hypervDomainSnapshotDelete(virDomainSnapshotPtr snapshot, + unsigned int flags) +{ + hypervPrivate *priv = snapshot->domain->conn->privateData; + g_autoptr(hypervInvokeParamsList) params = NULL; + g_auto(virBuffer) eprQuery = VIR_BUFFER_INITIALIZER; + g_autoptr(Msvm_VirtualSystemSettingData) snapshotVSSD = NULL; + + virCheckFlags(0, -1); + + snapshotVSSD = hypervDomainLookupSnapshotSD(snapshot->domain, snapshot->name); + if (!snapshotVSSD) + return -1; + + /* Build EPR for the snapshot to destroy */ + virBufferEscapeSQL(&eprQuery, + MSVM_VIRTUALSYSTEMSETTINGDATA_WQL_SELECT "WHERE InstanceID = '%s'", + snapshotVSSD->data->InstanceID); + + /* Destroy snapshot via DestroySnapshot method */ + params = hypervCreateInvokeParamsList("DestroySnapshot", + MSVM_VIRTUALSYSTEMSNAPSHOTSERVICE_SELECTOR, + Msvm_VirtualSystemSnapshotService_WmiInfo); + + if (!params) + return -1; + + if (hypervAddEprParam(params, "AffectedSnapshot", &eprQuery, + Msvm_VirtualSystemSettingData_WmiInfo) < 0) + return -1; + + /* Invoke the method */ + if (hypervInvokeMethod(priv, ¶ms, NULL) < 0) + return -1; + + return 0; +} + + static int hypervDomainListAllSnapshots(virDomainPtr domain, virDomainSnapshotPtr **snaps, @@ -4452,6 +4492,7 @@ static virHypervisorDriver hypervHypervisorDriver = { .domainHasCurrentSnapshot = hypervDomainHasCurrentSnapshot, /* 12.2.0 */ .domainSnapshotCurrent = hypervDomainSnapshotCurrent, /* 12.2.0 */ .domainSnapshotGetParent = hypervDomainSnapshotGetParent, /* 12.2.0 */ + .domainSnapshotDelete = hypervDomainSnapshotDelete, /* 12.2.0 */ }; -- 2.53.0
When invoking a method in WMI, it can either return synchronously or asynchronously (with return value 4096). In the latter case, the output parameters of the method are not present in the method response xml document. We have to fetch the output parameters via associations with the Job object that is returned in the method response. the hypervInvokeMethod() function already partially handles the async case by polling the job until it fails, completes successfully, or times out. This patch adds a utility function to fetch a named output parameter from a given method response xml document. It handles both synchronous and asynchronous cases. Signed-off-by: Jonathon Jongsma <jjongsma@redhat.com> --- src/hyperv/hyperv_wmi.c | 139 ++++++++++++++++++++++++++++++++++++++++ src/hyperv/hyperv_wmi.h | 6 ++ 2 files changed, 145 insertions(+) diff --git a/src/hyperv/hyperv_wmi.c b/src/hyperv/hyperv_wmi.c index 3e161429d5..1329d03262 100644 --- a/src/hyperv/hyperv_wmi.c +++ b/src/hyperv/hyperv_wmi.c @@ -866,6 +866,145 @@ hypervInvokeMethod(hypervPrivate *priv, return 0; } +static char* +hypervOutputParamReferenceId(WsXmlNodeH node) +{ + WsXmlNodeH selector_set = NULL; + WsXmlNodeH selector = NULL; + int i = 0; + + if (node) + selector_set = ws_xml_find_in_tree(node, XML_NS_WS_MAN, "SelectorSet", TRUE); + + if (selector_set) { + for (i = 0; (selector = ws_xml_get_child(selector_set, i, XML_NS_WS_MAN, "Selector")) != NULL; i++) { + if (STREQ_NULLABLE(ws_xml_find_attr_value(selector, NULL, "Name"), "InstanceID")) { + return ws_xml_get_node_text(selector); + } + } + } + return NULL; +} + + +/* + * hypervResponseGetOutputParam() + * + * Extracts an output parameter from a WMI method response and retrieves the + * referenced object. + * + * Handles both synchronous and asynchronous cases. When a method is + * synchronous, the parameter will be directly present in the response document. + * When the method returns asynchronously, we need to fetch the result parameter + * via its associations with the Msvm_ConcreteJob object referenced in the + * response document. + * + * Parameters: + * @priv: hypervPrivate object associated with the connection + * response: The WMI method response document + * paramName: The output parameter name (e.g., "ResultingSnapshot") + * paramClass: The WMI class info for the expected result type + * outParam: return location for the named output parameter + * + * Returns 0 on success, -1 on failure. + */ +int +hypervResponseGetOutputParam(hypervPrivate *priv, + WsXmlDocH response, + const char *paramName, + hypervWmiClassInfo *paramClassInfo, + hypervObject **outParam) +{ + g_auto(virBuffer) query = VIR_BUFFER_INITIALIZER; + WsXmlNodeH body = NULL; + WsXmlNodeH output_node = NULL; + char *output_name = NULL; + char *provider_ns = NULL; + const char *rv_str = NULL; + int return_code; + int i = 0; + + body = ws_xml_get_soap_body(response); + if (!body) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("Could not find SOAP Body in response")); + return -1; + } + + /* Find the $(METHOD)_OUTPUT node in the SOAP Body */ + for (i = 0; (output_node = ws_xml_get_child(body, i, NULL, NULL)) != NULL; i++) { + output_name = ws_xml_get_node_local_name(output_node); + if (output_name && g_str_has_suffix(output_name, "_OUTPUT")) + break; + output_node = NULL; + } + + if (!output_node) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("Could not find _OUTPUT node in method response")); + return -1; + } + + provider_ns = ws_xml_get_node_name_ns(output_node); + + rv_str = ws_xml_get_node_text(ws_xml_get_child(output_node, 0, provider_ns, "ReturnValue")); + if (!rv_str || virStrToLong_i(rv_str, NULL, 10, &return_code) < 0) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("Could not get ReturnValue from method response")); + return -1; + } + + if (return_code == CIM_RETURNCODE_COMPLETED_WITH_NO_ERROR) { + /* if this was a synchronous response, the output parameter should contain + * the id of an object, so we can simply look it up by its instance ID */ + WsXmlNodeH param_node = ws_xml_get_child(output_node, 0, provider_ns, paramName); + const char *out_param_id = NULL; + + if (param_node) + out_param_id = hypervOutputParamReferenceId(param_node); + + if (!out_param_id) { + virReportError(VIR_ERR_INTERNAL_ERROR, + _("Could not find output parameter '%1$s' in response"), + paramName); + return -1; + } + VIR_DEBUG("Method response was synchronous: %1$s = %2$s", paramName, out_param_id); + virBufferAsprintf(&query, "SELECT * FROM %s ", paramClassInfo->name); + virBufferEscapeSQL(&query, "WHERE InstanceID='%s'", out_param_id); + } else if (return_code == CIM_RETURNCODE_TRANSITION_STARTED) { + /* if this was an asynchronous response, we have to get the output + * parameter from its association with the async job */ + WsXmlNodeH job_node = ws_xml_get_child(output_node, 0, provider_ns, "Job"); + const char *job_id = NULL; + + if (job_node) + job_id = hypervOutputParamReferenceId(job_node); + + if (!job_id) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("Could not find Job ID in async method response")); + return -1; + } + VIR_DEBUG("Method response was asynchronous. Job ID = %1$s", job_id); + virBufferEscapeSQL(&query, + "ASSOCIATORS OF {Msvm_ConcreteJob.InstanceID='%s'} ", + job_id); + virBufferAsprintf(&query, + "WHERE AssocClass = Msvm_AffectedJobElement " + "ResultClass = %s", + paramClassInfo->name); + } else { + virReportError(VIR_ERR_INTERNAL_ERROR, + _("Unexpected return code %1$d in method response"), + return_code); + return -1; + } + + hypervGetWmiClassList(priv, paramClassInfo, &query, outParam); + return 0; +} + /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * Object */ diff --git a/src/hyperv/hyperv_wmi.h b/src/hyperv/hyperv_wmi.h index 0560f83e0d..36ca85a592 100644 --- a/src/hyperv/hyperv_wmi.h +++ b/src/hyperv/hyperv_wmi.h @@ -166,6 +166,12 @@ int hypervInvokeMethod(hypervPrivate *priv, hypervInvokeParamsList **paramsPtr, WsXmlDocH *res); +int hypervResponseGetOutputParam(hypervPrivate *priv, + WsXmlDocH response, + const char *paramName, + hypervWmiClassInfo *paramClassInfo, + hypervObject **outParam); + /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * CIM/Msvm_ReturnCode */ -- 2.53.0
Signed-off-by: Jonathon Jongsma <jjongsma@redhat.com> --- src/hyperv/hyperv_driver.c | 88 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 88 insertions(+) diff --git a/src/hyperv/hyperv_driver.c b/src/hyperv/hyperv_driver.c index 841d9ccaa5..5417c8593f 100644 --- a/src/hyperv/hyperv_driver.c +++ b/src/hyperv/hyperv_driver.c @@ -4163,6 +4163,93 @@ hypervDomainSnapshotLookupByName(virDomainPtr domain, } +static virDomainSnapshotPtr +hypervDomainSnapshotCreateXML(virDomainPtr domain, + const char *xmlDesc, + unsigned int flags) +{ + hypervPrivate *priv = domain->conn->privateData; + g_autoptr(Msvm_ComputerSystem) computerSystem = NULL; + g_autoptr(hypervInvokeParamsList) params = NULL; + g_autoptr(virDomainSnapshotDef) def = NULL; + g_autoptr(GHashTable) snapshotSettings = NULL; + g_auto(virBuffer) eprQuery = VIR_BUFFER_INITIALIZER; + g_auto(virBuffer) query = VIR_BUFFER_INITIALIZER; + g_autoptr(Msvm_VirtualSystemSettingData) snapshot = NULL; + g_auto(WsXmlDocH) response = NULL; + char uuid_string[VIR_UUID_STRING_BUFLEN]; + + virCheckFlags(VIR_DOMAIN_SNAPSHOT_CREATE_QUIESCE, NULL); + + def = virDomainSnapshotDefParseString(xmlDesc, priv->xmlopt, NULL, NULL, 0); + if (!def) + return NULL; + + if (def->ndisks) { + virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s", + _("configuring specific disks is not yet supported for hyperv")); + return NULL; + } + + if (def->memory) { + virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s", + _("configuring snapshot memory is not yet supported for hyperv")); + return NULL; + } + + virUUIDFormat(domain->uuid, uuid_string); + + if (hypervMsvmComputerSystemFromDomain(domain, &computerSystem) < 0) + return NULL; + + virBufferEscapeSQL(&eprQuery, + MSVM_COMPUTERSYSTEM_WQL_SELECT "WHERE Name = '%s'", + uuid_string); + + params = hypervCreateInvokeParamsList("CreateSnapshot", + MSVM_VIRTUALSYSTEMSNAPSHOTSERVICE_SELECTOR, + Msvm_VirtualSystemSnapshotService_WmiInfo); + if (!params) + return NULL; + + if (hypervAddEprParam(params, "AffectedSystem", &eprQuery, + Msvm_ComputerSystem_WmiInfo) < 0) + return NULL; + + snapshotSettings = hypervCreateEmbeddedParam(Msvm_VirtualSystemSnapshotSettingData_WmiInfo); + if (!snapshotSettings) + return NULL; + + if (hypervSetEmbeddedProperty(snapshotSettings, "ConsistencyLevel", + (flags & VIR_DOMAIN_SNAPSHOT_CREATE_QUIESCE) ? + HYPERV_SNAPSHOT_CONSISTENCY_APP_CONSISTENT : + HYPERV_SNAPSHOT_CONSISTENCY_CRASH_CONSISTENT) < 0) + return NULL; + + if (hypervAddEmbeddedParam(params, "SnapshotSettings", &snapshotSettings, + Msvm_VirtualSystemSnapshotSettingData_WmiInfo) < 0) + return NULL; + + hypervAddSimpleParam(params, "SnapshotType", HYPERV_SNAPSHOT_TYPE_FULL); + + if (hypervInvokeMethod(priv, ¶ms, &response) < 0) + return NULL; + + /* The CreateSnapshot method will generally return an async job rather + * than returning the 'ResultingSnapshot' output parameter directly + * in the response. Although hypervInvokeMethod() polls the async job to + * completion when this occurs, the response will not include the output + * parameters. So we use this helper function to build a query to fetch the + * resulting object from the async job associations. */ + if (hypervResponseGetOutputParam(priv, response, "ResultingSnapshot", + Msvm_VirtualSystemSettingData_WmiInfo, + (hypervObject**) &snapshot) < 0) + return NULL; + + return virGetDomainSnapshot(domain, snapshot->data->InstanceID); +} + + static int hypervDomainSnapshotDelete(virDomainSnapshotPtr snapshot, unsigned int flags) @@ -4493,6 +4580,7 @@ static virHypervisorDriver hypervHypervisorDriver = { .domainSnapshotCurrent = hypervDomainSnapshotCurrent, /* 12.2.0 */ .domainSnapshotGetParent = hypervDomainSnapshotGetParent, /* 12.2.0 */ .domainSnapshotDelete = hypervDomainSnapshotDelete, /* 12.2.0 */ + .domainSnapshotCreateXML = hypervDomainSnapshotCreateXML, /* 12.2.0 */ }; -- 2.53.0
participants (1)
-
Jonathon Jongsma