Signed-off-by: Jonathon Jongsma <jjongsma@redhat.com> --- src/hyperv/hyperv_driver.c | 82 ++++++++++++++++++++++ src/hyperv/hyperv_wmi.c | 139 +++++++++++++++++++++++++++++++++++++ src/hyperv/hyperv_wmi.h | 6 ++ 3 files changed, 227 insertions(+) diff --git a/src/hyperv/hyperv_driver.c b/src/hyperv/hyperv_driver.c index 841d9ccaa5..12af6bd337 100644 --- a/src/hyperv/hyperv_driver.c +++ b/src/hyperv/hyperv_driver.c @@ -4163,6 +4163,87 @@ 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", + _("disk snapshots 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 +4574,7 @@ static virHypervisorDriver hypervHypervisorDriver = { .domainSnapshotCurrent = hypervDomainSnapshotCurrent, /* 12.2.0 */ .domainSnapshotGetParent = hypervDomainSnapshotGetParent, /* 12.2.0 */ .domainSnapshotDelete = hypervDomainSnapshotDelete, /* 12.2.0 */ + .domainSnapshotCreateXML = hypervDomainSnapshotCreateXML, /* 12.2.0 */ }; diff --git a/src/hyperv/hyperv_wmi.c b/src/hyperv/hyperv_wmi.c index dab7abe8cf..9809eac1e5 100644 --- a/src/hyperv/hyperv_wmi.c +++ b/src/hyperv/hyperv_wmi.c @@ -867,6 +867,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