/* * hyperv_wmi.c: general WMI over WSMAN related functions and structures for * managing Microsoft Hyper-V hosts * * Copyright (C) 2017 Datto Inc * Copyright (C) 2014 Red Hat, Inc. * Copyright (C) 2011 Matthias Bolte * Copyright (C) 2009 Michael Sievers * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library. If not, see * . * */ #include #include #include "internal.h" #include "virerror.h" #include "datatypes.h" #include "viralloc.h" #include "viruuid.h" #include "virbuffer.h" #include "hyperv_private.h" #include "hyperv_wmi.h" #include "virstring.h" #include "openwsman.h" #include "virlog.h" #define WS_SERIALIZER_FREE_MEM_WORKS 0 #define VIR_FROM_THIS VIR_FROM_HYPERV #define HYPERV_JOB_TIMEOUT_MS 300000 VIR_LOG_INIT("hyperv.hyperv_wmi"); static int hypervGetWmiClassInfo(hypervPrivate *priv, hypervWmiClassInfoListPtr list, hypervWmiClassInfoPtr *info) { const char *version = "v2"; size_t i; if (list->count == 0) { virReportError(VIR_ERR_INTERNAL_ERROR, "%s", _("The WMI class info list is empty")); return -1; } /* if there's just one WMI class and isn't versioned, assume "shared" */ if (list->count == 1 && list->objs[0]->version == NULL) { *info = list->objs[0]; return 0; } if (priv->wmiVersion == HYPERV_WMI_VERSION_V1) version = "v1"; for (i = 0; i < list->count; i++) { if (STRCASEEQ(list->objs[i]->version, version)) { *info = list->objs[i]; return 0; } } virReportError(VIR_ERR_INTERNAL_ERROR, _("Could not match WMI class info for version %s"), version); return -1; } static int hypervGetWmiClassList(hypervPrivate *priv, hypervWmiClassInfoListPtr wmiInfo, virBufferPtr query, hypervObject **wmiClass) { hypervWqlQuery wqlQuery = HYPERV_WQL_QUERY_INITIALIZER; wqlQuery.info = wmiInfo; wqlQuery.query = query; return hypervEnumAndPull(priv, &wqlQuery, wmiClass); } int hypervVerifyResponse(WsManClient *client, WsXmlDocH response, const char *detail) { int lastError = wsmc_get_last_error(client); int responseCode = wsmc_get_response_code(client); WsManFault *fault; if (lastError != WS_LASTERR_OK) { virReportError(VIR_ERR_INTERNAL_ERROR, _("Transport error during %s: %s (%d)"), detail, wsman_transport_get_last_error_string(lastError), lastError); return -1; } /* Check the HTTP response code and report an error if it's not 200 (OK), * 400 (Bad Request) or 500 (Internal Server Error) */ if (responseCode != 200 && responseCode != 400 && responseCode != 500) { virReportError(VIR_ERR_INTERNAL_ERROR, _("Unexpected HTTP response during %s: %d"), detail, responseCode); return -1; } if (response == NULL) { virReportError(VIR_ERR_INTERNAL_ERROR, _("Empty response during %s"), detail); return -1; } if (wsmc_check_for_fault(response)) { fault = wsmc_fault_new(); if (fault == NULL) { virReportOOMError(); return -1; } wsmc_get_fault_data(response, fault); virReportError(VIR_ERR_INTERNAL_ERROR, _("SOAP fault during %s: code '%s', subcode '%s', " "reason '%s', detail '%s'"), detail, NULLSTR(fault->code), NULLSTR(fault->subcode), NULLSTR(fault->reason), NULLSTR(fault->fault_detail)); wsmc_fault_destroy(fault); return -1; } return 0; } /* * Methods to work with method invocation parameters */ /* * hypervCreateInvokeParamsList: * @priv: hypervPrivate object associated with the connection. * @method: The name of the method you are calling * @selector: The selector for the object you are invoking the method on * @obj: The WmiInfo of the object class you are invoking the method on. * * Create a new InvokeParamsList object for the method call. * * Returns a pointer to the newly instantiated object on success, which should * be freed by hypervInvokeMethod. Otherwise returns NULL. */ hypervInvokeParamsListPtr hypervCreateInvokeParamsList(hypervPrivate *priv, const char *method, const char *selector, hypervWmiClassInfoListPtr obj) { hypervInvokeParamsListPtr params = NULL; hypervWmiClassInfoPtr info = NULL; if (hypervGetWmiClassInfo(priv, obj, &info) < 0) goto cleanup; if (VIR_ALLOC(params) < 0) goto cleanup; if (VIR_ALLOC_N(params->params, HYPERV_DEFAULT_PARAM_COUNT) < 0) { VIR_FREE(params); goto cleanup; } params->method = method; params->ns = info->rootUri; params->resourceUri = info->resourceUri; params->selector = selector; params->nbParams = 0; params->nbAvailParams = HYPERV_DEFAULT_PARAM_COUNT; cleanup: return params; } /* * hypervFreeInvokeParams: * @params: Params object to be freed * */ void hypervFreeInvokeParams(hypervInvokeParamsListPtr params) { hypervParamPtr p = NULL; size_t i = 0; if (params == NULL) return; for (i = 0; i < params->nbParams; i++) { p = &(params->params[i]); switch (p->type) { case HYPERV_SIMPLE_PARAM: break; case HYPERV_EPR_PARAM: virBufferFreeAndReset(p->epr.query); break; case HYPERV_EMBEDDED_PARAM: hypervFreeEmbeddedParam(p->embedded.table); break; default: virReportError(VIR_ERR_INTERNAL_ERROR, "%s", _("Invalid parameter type passed to free")); } } VIR_DISPOSE_N(params->params, params->nbAvailParams); VIR_FREE(params); } static inline int hypervCheckParams(hypervInvokeParamsListPtr params) { if (params->nbParams + 1 > params->nbAvailParams) { if (VIR_EXPAND_N(params->params, params->nbAvailParams, 5) < 0) return -1; } return 0; } /* * hypervAddSimpleParam: * @params: Params object to add to * @name: Name of the parameter * @value: Value of the parameter * * Add a param of type HYPERV_SIMPLE_PARAM, which is essentially a serialized * key/value pair. * * Returns -1 on failure, 0 on success. */ int hypervAddSimpleParam(hypervInvokeParamsListPtr params, const char *name, const char *value) { int result = -1; hypervParamPtr p = NULL; if (hypervCheckParams(params) < 0) goto cleanup; p = ¶ms->params[params->nbParams]; p->type = HYPERV_SIMPLE_PARAM; p->simple.name = name; p->simple.value = value; params->nbParams++; result = 0; cleanup: return result; } /* * hypervAddEprParam: * @params: Params object to add to * @name: Parameter name * @priv: hypervPrivate object associated with the connection * @query: WQL filter * @eprInfo: WmiInfo of the object being filtered * * Adds an EPR param to the params list. Returns -1 on failure, 0 on success. */ int hypervAddEprParam(hypervInvokeParamsListPtr params, const char *name, hypervPrivate *priv, virBufferPtr query, hypervWmiClassInfoListPtr eprInfo) { hypervParamPtr p = NULL; hypervWmiClassInfoPtr classInfo = NULL; if (hypervGetWmiClassInfo(priv, eprInfo, &classInfo) < 0 || hypervCheckParams(params) < 0) return -1; p = ¶ms->params[params->nbParams]; p->type = HYPERV_EPR_PARAM; p->epr.name = name; p->epr.query = query; p->epr.info = classInfo; params->nbParams++; return 0; } /* * hypervCreateEmbeddedParam: * @priv: hypervPrivate object associated with the connection * @info: WmiInfo of the object type to serialize * * Instantiates a virHashTable pre-filled with all the properties pre-added * a key/value pairs set to NULL. The user then sets only those properties that * they wish to serialize, and passes the table via hypervAddEmbeddedParam. * * Returns a pointer to the virHashTable on success, otherwise NULL. */ virHashTablePtr hypervCreateEmbeddedParam(hypervPrivate *priv, hypervWmiClassInfoListPtr info) { size_t i; int count = 0; virHashTablePtr table = NULL; XmlSerializerInfo *typeinfo = NULL; XmlSerializerInfo *item = NULL; hypervWmiClassInfoPtr classInfo = NULL; /* Get the typeinfo out of the class info list */ if (hypervGetWmiClassInfo(priv, info, &classInfo) < 0) goto error; typeinfo = classInfo->serializerInfo; /* loop through the items to find out how many fields there are */ for (i = 0; typeinfo[i].name != NULL; i++) {} count = i; table = virHashCreate(count, NULL); if (table == NULL) goto error; for (i = 0; typeinfo[i].name != NULL; i++) { item = &typeinfo[i]; if (virHashAddEntry(table, item->name, NULL) < 0) goto error; } return table; error: virHashFree(table); return NULL; } int hypervSetEmbeddedProperty(virHashTablePtr table, const char *name, char *value) { return virHashUpdateEntry(table, name, value); } /* * hypervAddEmbeddedParam: * @params: Params list to add to * @priv: hypervPrivate object associated with the connection * @name: Name of the parameter * @table: table of properties to add * @info: WmiInfo of the object to serialize * * Add a virHashTable containing object properties as an embedded param to * an invocation list. Returns -1 on failure, 0 on success. */ int hypervAddEmbeddedParam(hypervInvokeParamsListPtr params, hypervPrivate *priv, const char *name, virHashTablePtr table, hypervWmiClassInfoListPtr info) { hypervParamPtr p = NULL; hypervWmiClassInfoPtr classInfo = NULL; if (hypervCheckParams(params) < 0) return -1; /* Get the typeinfo out of the class info list */ if (hypervGetWmiClassInfo(priv, info, &classInfo) < 0) return -1; p = ¶ms->params[params->nbParams]; p->type = HYPERV_EMBEDDED_PARAM; p->embedded.name = name; p->embedded.table = table; p->embedded.info = classInfo; params->nbParams++; return 0; } /* * hypervFreeEmbeddedParam: * @param: Pointer to embedded param to free * * Free the embedded param hash table. */ void hypervFreeEmbeddedParam(virHashTablePtr p) { virHashFree(p); } /* * Serializing parameters to XML and invoking methods */ static int hypervGetCimTypeInfo(hypervCimTypePtr typemap, const char *name, hypervCimTypePtr *property) { size_t i = 0; while (typemap[i].name[0] != '\0') { if (STREQ(typemap[i].name, name)) { *property = &typemap[i]; return 0; } i++; } return -1; } static int hypervCreateInvokeXmlDoc(hypervInvokeParamsListPtr params, WsXmlDocH *docRoot) { int result = -1; char *method = NULL; WsXmlNodeH xmlNodeMethod = NULL; if (virAsprintf(&method, "%s_INPUT", params->method) < 0) goto cleanup; *docRoot = ws_xml_create_doc(NULL, method); if (*docRoot == NULL) { virReportError(VIR_ERR_INTERNAL_ERROR, "%s", _("Could not instantiate XML document")); goto cleanup; } xmlNodeMethod = xml_parser_get_root(*docRoot); if (xmlNodeMethod == NULL) { virReportError(VIR_ERR_INTERNAL_ERROR, "%s", _("Could not get root node of XML document")); goto cleanup; } /* add resource URI as namespace */ ws_xml_set_ns(xmlNodeMethod, params->resourceUri, "p"); result = 0; cleanup: if (result < 0 && *docRoot != NULL) { ws_xml_destroy_doc(*docRoot); *docRoot = NULL; } VIR_FREE(method); return result; } static int hypervSerializeSimpleParam(hypervParamPtr p, const char *resourceUri, WsXmlNodeH *methodNode) { WsXmlNodeH xmlNodeParam = NULL; xmlNodeParam = ws_xml_add_child(*methodNode, resourceUri, p->simple.name, p->simple.value); if (xmlNodeParam == NULL) { virReportError(VIR_ERR_INTERNAL_ERROR, "%s", _("Could not create simple param")); return -1; } return 0; } static int hypervSerializeEprParam(hypervParamPtr p, hypervPrivate *priv, const char *resourceUri, WsXmlNodeH *methodNode) { int result = -1; WsXmlNodeH xmlNodeParam = NULL, xmlNodeTemp = NULL, xmlNodeAddr = NULL, xmlNodeRef = NULL; WsXmlDocH xmlDocResponse = NULL; WsXmlNsH ns = NULL; client_opt_t *options = NULL; filter_t *filter = NULL; char *enumContext = NULL; char *query_string = NULL; /* init and set up options */ options = wsmc_options_init(); if (!options) { virReportError(VIR_ERR_INTERNAL_ERROR, "%s", _("Could not init options")); goto cleanup; } wsmc_set_action_option(options, FLAG_ENUMERATION_ENUM_EPR); /* Get query and create filter based on it */ if (virBufferCheckError(p->epr.query) < 0) { virBufferFreeAndReset(p->epr.query); goto cleanup; } query_string = virBufferContentAndReset(p->epr.query); filter = filter_create_simple(WSM_WQL_FILTER_DIALECT, query_string); if (!filter) { virReportError(VIR_ERR_INTERNAL_ERROR, "%s", _("Could not create WQL filter")); goto cleanup; } /* enumerate based on the filter from this query */ xmlDocResponse = wsmc_action_enumerate(priv->client, p->epr.info->rootUri, options, filter); if (hypervVerifyResponse(priv->client, xmlDocResponse, "enumeration") < 0) goto cleanup; /* Get context */ enumContext = wsmc_get_enum_context(xmlDocResponse); ws_xml_destroy_doc(xmlDocResponse); /* Pull using filter and enum context */ xmlDocResponse = wsmc_action_pull(priv->client, resourceUri, options, filter, enumContext); if (hypervVerifyResponse(priv->client, xmlDocResponse, "pull") < 0) goto cleanup; /* drill down and extract EPR node children */ if (!(xmlNodeTemp = ws_xml_get_soap_body(xmlDocResponse))) { virReportError(VIR_ERR_INTERNAL_ERROR, "%s", _("Could not get SOAP body")); goto cleanup; } if (!(xmlNodeTemp = ws_xml_get_child(xmlNodeTemp, 0, XML_NS_ENUMERATION, WSENUM_PULL_RESP))) { virReportError(VIR_ERR_INTERNAL_ERROR, "%s", _("Could not get response")); goto cleanup; } if (!(xmlNodeTemp = ws_xml_get_child(xmlNodeTemp, 0, XML_NS_ENUMERATION, WSENUM_ITEMS))) { virReportError(VIR_ERR_INTERNAL_ERROR, "%s", _("Could not get response items")); goto cleanup; } if (!(xmlNodeTemp = ws_xml_get_child(xmlNodeTemp, 0, XML_NS_ADDRESSING, WSA_EPR))) { virReportError(VIR_ERR_INTERNAL_ERROR, "%s", _("Could not get EPR items")); goto cleanup; } if (!(xmlNodeAddr = ws_xml_get_child(xmlNodeTemp, 0, XML_NS_ADDRESSING, WSA_ADDRESS))) { virReportError(VIR_ERR_INTERNAL_ERROR, "%s", _("Could not get EPR address")); goto cleanup; } if (!(xmlNodeRef = ws_xml_get_child(xmlNodeTemp, 0, XML_NS_ADDRESSING, WSA_REFERENCE_PARAMETERS))) { virReportError(VIR_ERR_INTERNAL_ERROR, "%s", _("Could not lookup EPR item reference parameters")); goto cleanup; } /* now build a new xml doc with the EPR node children */ if (!(xmlNodeParam = ws_xml_add_child(*methodNode, resourceUri, p->epr.name, NULL))) { virReportError(VIR_ERR_INTERNAL_ERROR, "%s", _("Could not add child node to methodNode")); goto cleanup; } if (!(ns = ws_xml_ns_add(xmlNodeParam, "http://schemas.xmlsoap.org/ws/2004/08/addressing", "a"))) { virReportError(VIR_ERR_INTERNAL_ERROR, "%s", _("Could not set namespace address for xmlNodeParam")); goto cleanup; } if (!(ns = ws_xml_ns_add(xmlNodeParam, "http://schemas.dmtf.org/wbem/wsman/1/wsman.xsd", "w"))) { virReportError(VIR_ERR_INTERNAL_ERROR, "%s", _("Could not set wsman namespace address for xmlNodeParam")); goto cleanup; } ws_xml_duplicate_tree(xmlNodeParam, xmlNodeAddr); ws_xml_duplicate_tree(xmlNodeParam, xmlNodeRef); /* we did it! */ result = 0; cleanup: if (options != NULL) wsmc_options_destroy(options); if (filter != NULL) filter_destroy(filter); ws_xml_destroy_doc(xmlDocResponse); VIR_FREE(enumContext); VIR_FREE(query_string); return result; } static int hypervSerializeEmbeddedParam(hypervParamPtr p, const char *resourceUri, WsXmlNodeH *methodNode) { int result = -1; WsXmlNodeH xmlNodeInstance = NULL, xmlNodeProperty = NULL, xmlNodeParam = NULL, xmlNodeArray = NULL; WsXmlDocH xmlDocTemp = NULL, xmlDocCdata = NULL; char *cdataContent = NULL; xmlNodePtr xmlNodeCdata = NULL; hypervWmiClassInfoPtr classInfo = p->embedded.info; virHashKeyValuePairPtr items = NULL; hypervCimTypePtr property = NULL; ssize_t numKeys = -1; int len = 0, i = 0; if (!(xmlNodeParam = ws_xml_add_child(*methodNode, resourceUri, p->embedded.name, NULL))) { virReportError(VIR_ERR_INTERNAL_ERROR, _("Could not add child node %s"), p->embedded.name); goto cleanup; } /* create the temp xml doc */ /* start with the INSTANCE node */ if (!(xmlDocTemp = ws_xml_create_doc(NULL, "INSTANCE"))) { virReportError(VIR_ERR_INTERNAL_ERROR, "%s", _("Could not create temporary xml doc")); goto cleanup; } if (!(xmlNodeInstance = xml_parser_get_root(xmlDocTemp))) { virReportError(VIR_ERR_INTERNAL_ERROR, "%s", _("Could not get temp xml doc root")); goto cleanup; } /* add CLASSNAME node to INSTANCE node */ if (!(ws_xml_add_node_attr(xmlNodeInstance, NULL, "CLASSNAME", classInfo->name))) { virReportError(VIR_ERR_INTERNAL_ERROR, "%s", _("Could not add attribute to node")); goto cleanup; } /* retrieve parameters out of hash table */ numKeys = virHashSize(p->embedded.table); items = virHashGetItems(p->embedded.table, NULL); if (!items) { virReportError(VIR_ERR_INTERNAL_ERROR, "%s", _("Could not read embedded param hash table")); goto cleanup; } /* Add the parameters */ for (i = 0; i < numKeys; i++) { const char *name = items[i].key; const char *value = items[i].value; if (value != NULL) { if (hypervGetCimTypeInfo(classInfo->propertyInfo, name, &property) < 0) { virReportError(VIR_ERR_INTERNAL_ERROR, "%s", _("Could not read type information")); goto cleanup; } if (!(xmlNodeProperty = ws_xml_add_child(xmlNodeInstance, NULL, property->isArray ? "PROPERTY.ARRAY" : "PROPERTY", NULL))) { virReportError(VIR_ERR_INTERNAL_ERROR, "%s", _("Could not add child to XML node")); goto cleanup; } if (!(ws_xml_add_node_attr(xmlNodeProperty, NULL, "NAME", name))) { virReportError(VIR_ERR_INTERNAL_ERROR, "%s", _("Could not add attribute to XML node")); goto cleanup; } if (!(ws_xml_add_node_attr(xmlNodeProperty, NULL, "TYPE", property->type))) { virReportError(VIR_ERR_INTERNAL_ERROR, "%s", _("Could not add attribute to XML node")); goto cleanup; } /* If this attribute is an array, add VALUE.ARRAY node */ if (property->isArray) { if (!(xmlNodeArray = ws_xml_add_child(xmlNodeProperty, NULL, "VALUE.ARRAY", NULL))) { virReportError(VIR_ERR_INTERNAL_ERROR, "%s", _("Could not add child to XML node")); goto cleanup; } } /* add the child */ if (!(ws_xml_add_child(property->isArray ? xmlNodeArray : xmlNodeProperty, NULL, "VALUE", value))) { virReportError(VIR_ERR_INTERNAL_ERROR, "%s", _("Could not add child to XML node")); goto cleanup; } xmlNodeArray = NULL; xmlNodeProperty = NULL; } } /* create CDATA node */ ws_xml_dump_memory_node_tree(xmlNodeInstance, &cdataContent, &len); if (!(xmlNodeCdata = xmlNewCDataBlock((xmlDocPtr) xmlDocCdata, (xmlChar *)cdataContent, len))) { virReportError(VIR_ERR_INTERNAL_ERROR, "%s", _("Could not create CDATA element")); goto cleanup; } /* * Add CDATA node to the doc root * * FIXME: there is no openwsman wrapper for xmlNewCDataBlock, so instead * silence clang alignment warnings by casting to a void pointer first */ if (!(xmlAddChild((xmlNodePtr)(void *)xmlNodeParam, xmlNodeCdata))) { virReportError(VIR_ERR_INTERNAL_ERROR, "%s", _("Could not add CDATA to doc root")); goto cleanup; } /* we did it! */ result = 0; cleanup: VIR_FREE(items); ws_xml_destroy_doc(xmlDocCdata); ws_xml_destroy_doc(xmlDocTemp); ws_xml_free_memory(cdataContent); return result; } /* * hypervInvokeMethod: * @priv: hypervPrivate object associated with the connection * @params: object containing the all necessary information for method * invocation * @res: Optional out parameter to contain the response XML. * * Performs an invocation described by @params, and optionally returns the * XML containing the result. Returns -1 on failure, 0 on success. */ int hypervInvokeMethod(hypervPrivate *priv, hypervInvokeParamsListPtr params, WsXmlDocH *res) { int result = -1; size_t i = 0; int returnCode; WsXmlDocH paramsDocRoot = NULL; client_opt_t *options = NULL; WsXmlDocH response = NULL; WsXmlNodeH methodNode = NULL; char *returnValue_xpath = NULL; char *jobcode_instance_xpath = NULL; char *returnValue = NULL; char *instanceID = NULL; bool completed = false; virBuffer query = VIR_BUFFER_INITIALIZER; Msvm_ConcreteJob *job = NULL; int jobState = -1; hypervParamPtr p = NULL; int timeout = HYPERV_JOB_TIMEOUT_MS; if (hypervCreateInvokeXmlDoc(params, ¶msDocRoot) < 0) { virReportError(VIR_ERR_INTERNAL_ERROR, "%s", _("Could not create XML document")); goto cleanup; } methodNode = xml_parser_get_root(paramsDocRoot); if (!methodNode) { virReportError(VIR_ERR_INTERNAL_ERROR, "%s", _("Could not get root of XML document")); goto cleanup; } /* Serialize parameters */ for (i = 0; i < params->nbParams; i++) { p = &(params->params[i]); switch (p->type) { case HYPERV_SIMPLE_PARAM: if (hypervSerializeSimpleParam(p, params->resourceUri, &methodNode) < 0) goto cleanup; break; case HYPERV_EPR_PARAM: if (hypervSerializeEprParam(p, priv, params->resourceUri, &methodNode) < 0) goto cleanup; break; case HYPERV_EMBEDDED_PARAM: if (hypervSerializeEmbeddedParam(p, params->resourceUri, &methodNode) < 0) goto cleanup; break; default: virReportError(VIR_ERR_INTERNAL_ERROR, "%s", _("Unknown parameter type")); goto cleanup; } } /* Invoke the method and get the response */ options = wsmc_options_init(); if (!options) { virReportError(VIR_ERR_INTERNAL_ERROR, "%s", _("Could not init options")); goto cleanup; } wsmc_add_selectors_from_str(options, params->selector); /* do the invoke */ response = wsmc_action_invoke(priv->client, params->resourceUri, options, params->method, paramsDocRoot); /* check return code of invocation */ if (virAsprintf(&returnValue_xpath, "/s:Envelope/s:Body/p:%s_OUTPUT/p:ReturnValue", params->method) < 0) goto cleanup; returnValue = ws_xml_get_xpath_value(response, returnValue_xpath); if (!returnValue) { virReportError(VIR_ERR_INTERNAL_ERROR, _("Could not get return value for %s invocation"), params->method); goto cleanup; } if (virStrToLong_i(returnValue, NULL, 10, &returnCode) < 0) goto cleanup; if (returnCode == CIM_RETURNCODE_TRANSITION_STARTED) { if (virAsprintf(&jobcode_instance_xpath, "/s:Envelope/s:Body/p:%s_OUTPUT/p:Job/a:ReferenceParameters/" "w:SelectorSet/w:Selector[@Name='InstanceID']", params->method) < 0) { goto cleanup; } instanceID = ws_xml_get_xpath_value(response, jobcode_instance_xpath); if (!instanceID) { virReportError(VIR_ERR_INTERNAL_ERROR, _("Could not get instance ID for %s invocation"), params->method); goto cleanup; } /* * Poll Hyper-V about the job until either the job completes or fails, * or 5 minutes have elapsed. * * Windows has its own timeout on running WMI method calls (it calls * these "jobs"), by default set to 1 minute. The administrator can * change this to whatever they want, however, so we can't rely on it. * * Therefore, to avoid waiting in this loop for a very long-running job * to complete, we instead bail after 5 minutes no matter what. NOTE that * this does not mean that the remote job has terminated on the Windows * side! That is up to Windows to control, we don't do anything about it. */ while (!completed && timeout >= 0) { virBufferAddLit(&query, MSVM_CONCRETEJOB_WQL_SELECT); virBufferEscapeSQL(&query, "where InstanceID = \"%s\"", instanceID); if (hypervGetMsvmConcreteJobList(priv, &query, &job) < 0 || job == NULL) goto cleanup; jobState = job->data.common->JobState; switch (jobState) { case MSVM_CONCRETEJOB_JOBSTATE_NEW: case MSVM_CONCRETEJOB_JOBSTATE_STARTING: case MSVM_CONCRETEJOB_JOBSTATE_RUNNING: case MSVM_CONCRETEJOB_JOBSTATE_SHUTTING_DOWN: hypervFreeObject(priv, (hypervObject *)job); job = NULL; usleep(100 * 1000); /* sleep 100 ms */ timeout -= 100; continue; case MSVM_CONCRETEJOB_JOBSTATE_COMPLETED: completed = true; break; case MSVM_CONCRETEJOB_JOBSTATE_TERMINATED: case MSVM_CONCRETEJOB_JOBSTATE_KILLED: case MSVM_CONCRETEJOB_JOBSTATE_EXCEPTION: case MSVM_CONCRETEJOB_JOBSTATE_SERVICE: goto cleanup; default: virReportError(VIR_ERR_INTERNAL_ERROR, "%s", _("Unknown invocation state")); goto cleanup; } } if (!completed && timeout < 0) { virReportError(VIR_ERR_OPERATION_TIMEOUT, _("Timeout waiting for %s invocation"), params->method); goto cleanup; } } else if (returnCode != CIM_RETURNCODE_COMPLETED_WITH_NO_ERROR) { virReportError(VIR_ERR_INTERNAL_ERROR, _("Invocation of %s returned an error: %s (%d)"), params->method, hypervReturnCodeToString(returnCode), returnCode); goto cleanup; } if (res) *res = response; result = 0; cleanup: if (options) wsmc_options_destroy(options); if (response && (!res)) ws_xml_destroy_doc(response); if (paramsDocRoot) ws_xml_destroy_doc(paramsDocRoot); VIR_FREE(returnValue_xpath); VIR_FREE(jobcode_instance_xpath); VIR_FREE(returnValue); VIR_FREE(instanceID); virBufferFreeAndReset(&query); hypervFreeObject(priv, (hypervObject *)job); hypervFreeInvokeParams(params); return result; } /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * Object */ /* This function guarantees that wqlQuery->query is reset, even on failure */ int hypervEnumAndPull(hypervPrivate *priv, hypervWqlQueryPtr wqlQuery, hypervObject **list) { int result = -1; WsSerializerContextH serializerContext; client_opt_t *options = NULL; char *query_string = NULL; hypervWmiClassInfoPtr wmiInfo = NULL; filter_t *filter = NULL; WsXmlDocH response = NULL; char *enumContext = NULL; hypervObject *head = NULL; hypervObject *tail = NULL; WsXmlNodeH node = NULL; XML_TYPE_PTR data = NULL; hypervObject *object; if (virBufferCheckError(wqlQuery->query) < 0) { virBufferFreeAndReset(wqlQuery->query); return -1; } query_string = virBufferContentAndReset(wqlQuery->query); if (list == NULL || *list != NULL) { virReportError(VIR_ERR_INTERNAL_ERROR, "%s", _("Invalid argument")); VIR_FREE(query_string); return -1; } if (hypervGetWmiClassInfo(priv, wqlQuery->info, &wmiInfo) < 0) goto cleanup; serializerContext = wsmc_get_serialization_context(priv->client); options = wsmc_options_init(); if (options == NULL) { virReportError(VIR_ERR_INTERNAL_ERROR, "%s", _("Could not initialize options")); goto cleanup; } filter = filter_create_simple(WSM_WQL_FILTER_DIALECT, query_string); if (filter == NULL) { virReportError(VIR_ERR_INTERNAL_ERROR, "%s", _("Could not create filter")); goto cleanup; } response = wsmc_action_enumerate(priv->client, wmiInfo->rootUri, options, filter); if (hypervVerifyResponse(priv->client, response, "enumeration") < 0) goto cleanup; enumContext = wsmc_get_enum_context(response); ws_xml_destroy_doc(response); response = NULL; while (enumContext != NULL && *enumContext != '\0') { response = wsmc_action_pull(priv->client, wmiInfo->resourceUri, options, filter, enumContext); if (hypervVerifyResponse(priv->client, response, "pull") < 0) goto cleanup; node = ws_xml_get_soap_body(response); if (node == NULL) { virReportError(VIR_ERR_INTERNAL_ERROR, "%s", _("Could not lookup SOAP body")); goto cleanup; } node = ws_xml_get_child(node, 0, XML_NS_ENUMERATION, WSENUM_PULL_RESP); if (node == NULL) { virReportError(VIR_ERR_INTERNAL_ERROR, "%s", _("Could not lookup pull response")); goto cleanup; } node = ws_xml_get_child(node, 0, XML_NS_ENUMERATION, WSENUM_ITEMS); if (node == NULL) { virReportError(VIR_ERR_INTERNAL_ERROR, "%s", _("Could not lookup pull response items")); goto cleanup; } if (ws_xml_get_child(node, 0, wmiInfo->resourceUri, wmiInfo->name) == NULL) break; data = ws_deserialize(serializerContext, node, wmiInfo->serializerInfo, wmiInfo->name, wmiInfo->resourceUri, NULL, 0, 0); if (data == NULL) { virReportError(VIR_ERR_INTERNAL_ERROR, "%s", _("Could not deserialize pull response item")); goto cleanup; } if (VIR_ALLOC(object) < 0) goto cleanup; object->info = wmiInfo; object->data.common = data; data = NULL; if (head == NULL) { head = object; } else { tail->next = object; } tail = object; VIR_FREE(enumContext); enumContext = wsmc_get_enum_context(response); ws_xml_destroy_doc(response); response = NULL; } *list = head; head = NULL; result = 0; cleanup: if (options != NULL) wsmc_options_destroy(options); if (filter != NULL) filter_destroy(filter); if (data != NULL) { #if WS_SERIALIZER_FREE_MEM_WORKS /* FIXME: ws_serializer_free_mem is broken in openwsman <= 2.2.6, * see hypervFreeObject for a detailed explanation. */ if (ws_serializer_free_mem(serializerContext, data, wmiInfo->serializerInfo) < 0) { VIR_ERROR(_("Could not free deserialized data")); } #endif } VIR_FREE(query_string); ws_xml_destroy_doc(response); VIR_FREE(enumContext); hypervFreeObject(priv, head); return result; } void hypervFreeObject(hypervPrivate *priv ATTRIBUTE_UNUSED, hypervObject *object) { hypervObject *next; #if WS_SERIALIZER_FREE_MEM_WORKS WsSerializerContextH serializerContext; #endif if (object == NULL) return; #if WS_SERIALIZER_FREE_MEM_WORKS serializerContext = wsmc_get_serialization_context(priv->client); #endif while (object != NULL) { next = object->next; #if WS_SERIALIZER_FREE_MEM_WORKS /* FIXME: ws_serializer_free_mem is broken in openwsman <= 2.2.6, * but this is not that critical, because openwsman keeps * track of all allocations of the deserializer and frees * them in wsmc_release. So this doesn't result in a real * memory leak, but just in piling up unused memory until * the connection is closed. */ if (ws_serializer_free_mem(serializerContext, object->data.common, object->info->serializerInfo) < 0) { VIR_ERROR(_("Could not free deserialized data")); } #endif VIR_FREE(object); object = next; } } /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * CIM/Msvm_ReturnCode */ const char * hypervReturnCodeToString(int returnCode) { switch (returnCode) { case CIM_RETURNCODE_COMPLETED_WITH_NO_ERROR: return _("Completed with no error"); case CIM_RETURNCODE_NOT_SUPPORTED: return _("Not supported"); case CIM_RETURNCODE_UNKNOWN_ERROR: return _("Unknown error"); case CIM_RETURNCODE_CANNOT_COMPLETE_WITHIN_TIMEOUT_PERIOD: return _("Cannot complete within timeout period"); case CIM_RETURNCODE_FAILED: return _("Failed"); case CIM_RETURNCODE_INVALID_PARAMETER: return _("Invalid parameter"); case CIM_RETURNCODE_IN_USE: return _("In use"); case CIM_RETURNCODE_TRANSITION_STARTED: return _("Transition started"); case CIM_RETURNCODE_INVALID_STATE_TRANSITION: return _("Invalid state transition"); case CIM_RETURNCODE_TIMEOUT_PARAMETER_NOT_SUPPORTED: return _("Timeout parameter not supported"); case CIM_RETURNCODE_BUSY: return _("Busy"); case MSVM_RETURNCODE_FAILED: return _("Failed"); case MSVM_RETURNCODE_ACCESS_DENIED: return _("Access denied"); case MSVM_RETURNCODE_NOT_SUPPORTED: return _("Not supported"); case MSVM_RETURNCODE_STATUS_IS_UNKNOWN: return _("Status is unknown"); case MSVM_RETURNCODE_TIMEOUT: return _("Timeout"); case MSVM_RETURNCODE_INVALID_PARAMETER: return _("Invalid parameter"); case MSVM_RETURNCODE_SYSTEM_IS_IN_USE: return _("System is in use"); case MSVM_RETURNCODE_INVALID_STATE_FOR_THIS_OPERATION: return _("Invalid state for this operation"); case MSVM_RETURNCODE_INCORRECT_DATA_TYPE: return _("Incorrect data type"); case MSVM_RETURNCODE_SYSTEM_IS_NOT_AVAILABLE: return _("System is not available"); case MSVM_RETURNCODE_OUT_OF_MEMORY: return _("Out of memory"); default: return _("Unknown return code"); } } /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * Generic "Get WMI class list" helpers */ int hypervGetMsvmComputerSystemList(hypervPrivate *priv, virBufferPtr query, Msvm_ComputerSystem **list) { return hypervGetWmiClassList(priv, Msvm_ComputerSystem_WmiInfo, query, (hypervObject **)list); } int hypervGetMsvmConcreteJobList(hypervPrivate *priv, virBufferPtr query, Msvm_ConcreteJob **list) { return hypervGetWmiClassList(priv, Msvm_ConcreteJob_WmiInfo, query, (hypervObject **)list); } int hypervGetWin32ComputerSystemList(hypervPrivate *priv, virBufferPtr query, Win32_ComputerSystem **list) { return hypervGetWmiClassList(priv, Win32_ComputerSystem_WmiInfo, query, (hypervObject **)list); } int hypervGetWin32ProcessorList(hypervPrivate *priv, virBufferPtr query, Win32_Processor **list) { return hypervGetWmiClassList(priv, Win32_Processor_WmiInfo, query, (hypervObject **)list); } int hypervGetMsvmVirtualSystemSettingDataList(hypervPrivate *priv, virBufferPtr query, Msvm_VirtualSystemSettingData **list) { return hypervGetWmiClassList(priv, Msvm_VirtualSystemSettingData_WmiInfo, query, (hypervObject **)list); } int hypervGetMsvmProcessorSettingDataList(hypervPrivate *priv, virBufferPtr query, Msvm_ProcessorSettingData **list) { return hypervGetWmiClassList(priv, Msvm_ProcessorSettingData_WmiInfo, query, (hypervObject **)list); } int hypervGetMsvmMemorySettingDataList(hypervPrivate *priv, virBufferPtr query, Msvm_MemorySettingData **list) { return hypervGetWmiClassList(priv, Msvm_MemorySettingData_WmiInfo, query, (hypervObject **)list); } int hypervGetMsvmKeyboardList(hypervPrivate *priv, virBufferPtr query, Msvm_Keyboard **list) { return hypervGetWmiClassList(priv, Msvm_Keyboard_WmiInfo, query, (hypervObject **)list); } /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * Msvm_ComputerSystem */ int hypervInvokeMsvmComputerSystemRequestStateChange(virDomainPtr domain, int requestedState) { int result = -1; hypervPrivate *priv = domain->conn->privateData; char uuid_string[VIR_UUID_STRING_BUFLEN]; WsXmlDocH response = NULL; client_opt_t *options = NULL; char *selector = NULL; char *properties = NULL; char *returnValue = NULL; int returnCode; char *instanceID = NULL; virBuffer query = VIR_BUFFER_INITIALIZER; Msvm_ConcreteJob *concreteJob = NULL; bool completed = false; const char *resourceUri = MSVM_COMPUTERSYSTEM_V2_RESOURCE_URI; virUUIDFormat(domain->uuid, uuid_string); if (virAsprintf(&selector, "Name=%s&CreationClassName=Msvm_ComputerSystem", uuid_string) < 0 || virAsprintf(&properties, "RequestedState=%d", requestedState) < 0) goto cleanup; if (priv->wmiVersion == HYPERV_WMI_VERSION_V1) resourceUri = MSVM_COMPUTERSYSTEM_V1_RESOURCE_URI; options = wsmc_options_init(); if (options == NULL) { virReportError(VIR_ERR_INTERNAL_ERROR, "%s", _("Could not initialize options")); goto cleanup; } wsmc_add_selectors_from_str(options, selector); wsmc_add_prop_from_str(options, properties); /* Invoke method */ response = wsmc_action_invoke(priv->client, resourceUri, options, "RequestStateChange", NULL); if (hypervVerifyResponse(priv->client, response, "invocation") < 0) goto cleanup; /* Check return value */ returnValue = ws_xml_get_xpath_value(response, (char *)"/s:Envelope/s:Body/p:RequestStateChange_OUTPUT/p:ReturnValue"); if (returnValue == NULL) { virReportError(VIR_ERR_INTERNAL_ERROR, _("Could not lookup %s for %s invocation"), "ReturnValue", "RequestStateChange"); goto cleanup; } if (virStrToLong_i(returnValue, NULL, 10, &returnCode) < 0) { virReportError(VIR_ERR_INTERNAL_ERROR, _("Could not parse return code from '%s'"), returnValue); goto cleanup; } if (returnCode == CIM_RETURNCODE_TRANSITION_STARTED) { /* Get concrete job object */ instanceID = ws_xml_get_xpath_value(response, (char *)"/s:Envelope/s:Body/p:RequestStateChange_OUTPUT/p:Job/a:ReferenceParameters/w:SelectorSet/w:Selector[@Name='InstanceID']"); if (instanceID == NULL) { virReportError(VIR_ERR_INTERNAL_ERROR, _("Could not lookup %s for %s invocation"), "InstanceID", "RequestStateChange"); goto cleanup; } /* FIXME: Poll every 100ms until the job completes or fails. There * seems to be no other way than polling. */ while (!completed) { virBufferAddLit(&query, MSVM_CONCRETEJOB_WQL_SELECT); virBufferAsprintf(&query, "where InstanceID = \"%s\"", instanceID); if (hypervGetMsvmConcreteJobList(priv, &query, &concreteJob) < 0) goto cleanup; if (concreteJob == NULL) { virReportError(VIR_ERR_INTERNAL_ERROR, _("Could not lookup %s for %s invocation"), "Msvm_ConcreteJob", "RequestStateChange"); goto cleanup; } switch (concreteJob->data.common->JobState) { case MSVM_CONCRETEJOB_JOBSTATE_NEW: case MSVM_CONCRETEJOB_JOBSTATE_STARTING: case MSVM_CONCRETEJOB_JOBSTATE_RUNNING: case MSVM_CONCRETEJOB_JOBSTATE_SHUTTING_DOWN: hypervFreeObject(priv, (hypervObject *)concreteJob); concreteJob = NULL; usleep(100 * 1000); continue; case MSVM_CONCRETEJOB_JOBSTATE_COMPLETED: completed = true; break; case MSVM_CONCRETEJOB_JOBSTATE_TERMINATED: case MSVM_CONCRETEJOB_JOBSTATE_KILLED: case MSVM_CONCRETEJOB_JOBSTATE_EXCEPTION: case MSVM_CONCRETEJOB_JOBSTATE_SERVICE: virReportError(VIR_ERR_INTERNAL_ERROR, _("Concrete job for %s invocation is in error state"), "RequestStateChange"); goto cleanup; default: virReportError(VIR_ERR_INTERNAL_ERROR, _("Concrete job for %s invocation is in unknown state"), "RequestStateChange"); goto cleanup; } } } else if (returnCode != CIM_RETURNCODE_COMPLETED_WITH_NO_ERROR) { virReportError(VIR_ERR_INTERNAL_ERROR, _("Invocation of %s returned an error: %s (%d)"), "RequestStateChange", hypervReturnCodeToString(returnCode), returnCode); goto cleanup; } result = 0; cleanup: if (options != NULL) wsmc_options_destroy(options); ws_xml_destroy_doc(response); VIR_FREE(selector); VIR_FREE(properties); VIR_FREE(returnValue); VIR_FREE(instanceID); hypervFreeObject(priv, (hypervObject *)concreteJob); return result; } int hypervMsvmComputerSystemEnabledStateToDomainState (Msvm_ComputerSystem *computerSystem) { switch (computerSystem->data.common->EnabledState) { case MSVM_COMPUTERSYSTEM_ENABLEDSTATE_UNKNOWN: return VIR_DOMAIN_NOSTATE; case MSVM_COMPUTERSYSTEM_ENABLEDSTATE_ENABLED: return VIR_DOMAIN_RUNNING; case MSVM_COMPUTERSYSTEM_ENABLEDSTATE_DISABLED: return VIR_DOMAIN_SHUTOFF; case MSVM_COMPUTERSYSTEM_ENABLEDSTATE_PAUSED: return VIR_DOMAIN_PAUSED; case MSVM_COMPUTERSYSTEM_ENABLEDSTATE_SUSPENDED: /* managed save */ return VIR_DOMAIN_SHUTOFF; case MSVM_COMPUTERSYSTEM_ENABLEDSTATE_STARTING: case MSVM_COMPUTERSYSTEM_ENABLEDSTATE_SNAPSHOTTING: case MSVM_COMPUTERSYSTEM_ENABLEDSTATE_SAVING: return VIR_DOMAIN_RUNNING; case MSVM_COMPUTERSYSTEM_ENABLEDSTATE_STOPPING: return VIR_DOMAIN_SHUTDOWN; case MSVM_COMPUTERSYSTEM_ENABLEDSTATE_PAUSING: case MSVM_COMPUTERSYSTEM_ENABLEDSTATE_RESUMING: return VIR_DOMAIN_RUNNING; default: return VIR_DOMAIN_NOSTATE; } } bool hypervIsMsvmComputerSystemActive(Msvm_ComputerSystem *computerSystem, bool *in_transition) { if (in_transition != NULL) *in_transition = false; switch (computerSystem->data.common->EnabledState) { case MSVM_COMPUTERSYSTEM_ENABLEDSTATE_UNKNOWN: return false; case MSVM_COMPUTERSYSTEM_ENABLEDSTATE_ENABLED: return true; case MSVM_COMPUTERSYSTEM_ENABLEDSTATE_DISABLED: return false; case MSVM_COMPUTERSYSTEM_ENABLEDSTATE_PAUSED: return true; case MSVM_COMPUTERSYSTEM_ENABLEDSTATE_SUSPENDED: /* managed save */ return false; case MSVM_COMPUTERSYSTEM_ENABLEDSTATE_STARTING: case MSVM_COMPUTERSYSTEM_ENABLEDSTATE_SNAPSHOTTING: case MSVM_COMPUTERSYSTEM_ENABLEDSTATE_SAVING: case MSVM_COMPUTERSYSTEM_ENABLEDSTATE_STOPPING: case MSVM_COMPUTERSYSTEM_ENABLEDSTATE_PAUSING: case MSVM_COMPUTERSYSTEM_ENABLEDSTATE_RESUMING: if (in_transition != NULL) *in_transition = true; return true; default: return false; } } int hypervMsvmComputerSystemToDomain(virConnectPtr conn, Msvm_ComputerSystem *computerSystem, virDomainPtr *domain) { unsigned char uuid[VIR_UUID_BUFLEN]; int id = -1; if (domain == NULL || *domain != NULL) { virReportError(VIR_ERR_INTERNAL_ERROR, "%s", _("Invalid argument")); return -1; } if (virUUIDParse(computerSystem->data.common->Name, uuid) < 0) { virReportError(VIR_ERR_INTERNAL_ERROR, _("Could not parse UUID from string '%s'"), computerSystem->data.common->Name); return -1; } if (hypervIsMsvmComputerSystemActive(computerSystem, NULL)) id = computerSystem->data.common->ProcessID; *domain = virGetDomain(conn, computerSystem->data.common->ElementName, uuid, id); return *domain ? 0 : -1; } int hypervMsvmComputerSystemFromDomain(virDomainPtr domain, Msvm_ComputerSystem **computerSystem) { hypervPrivate *priv = domain->conn->privateData; char uuid_string[VIR_UUID_STRING_BUFLEN]; virBuffer query = VIR_BUFFER_INITIALIZER; if (computerSystem == NULL || *computerSystem != NULL) { virReportError(VIR_ERR_INTERNAL_ERROR, "%s", _("Invalid argument")); return -1; } virUUIDFormat(domain->uuid, uuid_string); virBufferAddLit(&query, MSVM_COMPUTERSYSTEM_WQL_SELECT); virBufferAddLit(&query, "where "); virBufferAddLit(&query, MSVM_COMPUTERSYSTEM_WQL_VIRTUAL); virBufferAsprintf(&query, "and Name = \"%s\"", uuid_string); if (hypervGetMsvmComputerSystemList(priv, &query, computerSystem) < 0) return -1; if (*computerSystem == NULL) { virReportError(VIR_ERR_NO_DOMAIN, _("No domain with UUID %s"), uuid_string); return -1; } return 0; } /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * Msvm_VirtualSystemSettingData */ int hypervGetMsvmVirtualSystemSettingDataFromUUID(hypervPrivate *priv, const char *uuid_string, Msvm_VirtualSystemSettingData **list) { virBuffer query = VIR_BUFFER_INITIALIZER; virBufferAsprintf(&query, "associators of " "{Msvm_ComputerSystem.CreationClassName=\"Msvm_ComputerSystem\"," "Name=\"%s\"} " "where AssocClass = Msvm_SettingsDefineState " "ResultClass = Msvm_VirtualSystemSettingData", uuid_string); if (hypervGetWmiClassList(priv, Msvm_VirtualSystemSettingData_WmiInfo, &query, (hypervObject **)list) < 0 || *list == NULL) return -1; return 0; } /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * Msvm_MemorySettingData */ int hypervGetMsvmMemorySettingDataFromVSSD(hypervPrivate *priv, const char *vssd_instanceid, Msvm_MemorySettingData **list) { virBuffer query = VIR_BUFFER_INITIALIZER; virBufferAsprintf(&query, "associators of " "{Msvm_VirtualSystemSettingData.InstanceID=\"%s\"} " "where AssocClass = Msvm_VirtualSystemSettingDataComponent " "ResultClass = Msvm_MemorySettingData", vssd_instanceid); if (hypervGetWmiClassList(priv, Msvm_MemorySettingData_WmiInfo, &query, (hypervObject **)list) < 0 || *list == NULL) return -1; return 0; }