This adds initial support for virDomainGetBlockInfo() for the hyperv driver. It currently supports: - physical disk drives that are assigned to a vm - virtual disk drives backed by a .VHD file that are local to the host - other drives backed by local files (e.g. cdrom with a .iso) It will fail to get allocation and physical values for any drives backed by files that are not local to the host (e.g. on network shares) Signed-off-by: Jonathon Jongsma <jjongsma@redhat.com> --- src/hyperv/hyperv_driver.c | 205 ++++++++++++++++++++++++++ src/hyperv/hyperv_wmi.c | 41 ++++++ src/hyperv/hyperv_wmi.h | 7 + src/hyperv/hyperv_wmi_generator.input | 109 ++++++++++++++ 4 files changed, 362 insertions(+) diff --git a/src/hyperv/hyperv_driver.c b/src/hyperv/hyperv_driver.c index 3cebab305b..e332b8a860 100644 --- a/src/hyperv/hyperv_driver.c +++ b/src/hyperv/hyperv_driver.c @@ -26,6 +26,9 @@ #include "internal.h" #include "datatypes.h" +#include "libvirt/libvirt.h" +#include "libvirt/virterror.h" +#include "virbuffer.h" #include "virdomainobjlist.h" #include "virauth.h" #include "viralloc.h" @@ -3822,6 +3825,207 @@ hypervDomainInterfaceAddresses(virDomainPtr dom, } +static int +hypervGetFileSize(hypervPrivate *priv, + const char *filePath, + unsigned long long *fileSize) +{ + g_autoptr(CIM_DataFile) dataFile = NULL; + g_auto(virBuffer) query = VIR_BUFFER_INITIALIZER; + g_autofree char *escapedPath = NULL; + + virBufferAddLit(&query, CIM_DATAFILE_WQL_SELECT); + virBufferEscapeSQL(&query, "WHERE Name='%s'", filePath); + + if (hypervGetWmiClass(CIM_DataFile, &dataFile) < 0 || !dataFile) { + virReportError(VIR_ERR_INTERNAL_ERROR, + _("Could not query file size for '%1$s'"), filePath); + return -1; + } + + *fileSize = dataFile->data->FileSize; + return 0; +} + + +static int +hypervGetPhysicalDiskBlockInfo(hypervPrivate *priv, + unsigned int driveNumber, + virDomainBlockInfoPtr info) +{ + g_autoptr(Win32_DiskDrive) diskDrive = NULL; + g_auto(virBuffer) query = VIR_BUFFER_INITIALIZER; + + virBufferAsprintf(&query, WIN32_DISKDRIVE_WQL_SELECT "WHERE Index=%u", driveNumber); + + if (hypervGetWmiClass(Win32_DiskDrive, &diskDrive) < 0 || !diskDrive) { + virReportError(VIR_ERR_INVALID_ARG, + _("Could not find physical disk with drive number %1$u"), driveNumber); + return -1; + } + + info->capacity = info->allocation = info->physical = diskDrive->data->Size; + return 0; +} + + +static int +hypervGetVHDCapacity(hypervPrivate *priv, + const char *path, + unsigned long long *capacity) +{ + g_auto(WsXmlDocH) settingDataDoc = NULL; + g_autofree char *maxInternalSizeStr = NULL; + + if (hypervImageManagementServiceGetVHDSD(priv, path, &settingDataDoc) < 0) + return -1; + + maxInternalSizeStr = ws_xml_get_xpath_value(settingDataDoc, + (char *)"//PROPERTY[@NAME='MaxInternalSize']/VALUE"); + + if (!maxInternalSizeStr) { + virReportError(VIR_ERR_INTERNAL_ERROR, + _("Could not find MaxInternalSize in VHD SettingData for '%1$s'"), path); + return -1; + } + + if (virStrToLong_ull(maxInternalSizeStr, NULL, 10, capacity) < 0) { + virReportError(VIR_ERR_INTERNAL_ERROR, + _("Failed to parse MaxInternalSize '%1$s' for '%2$s'"), + maxInternalSizeStr, path); + return -1; + } + + return 0; +} + + +static int +hypervGetVirtualDiskBlockInfo(hypervPrivate *priv, + const char *diskpath, + virDomainBlockInfoPtr info) +{ + unsigned long long capacity = 0; + unsigned long long allocation = 0; + /* This might fail if diskpath is not a vhd file, but continue anyway as it + * might be e.g. an ISO that is not supported by the ImageManagementService */ + int rcapacity = hypervGetVHDCapacity(priv, diskpath, &capacity); + + /* querying actual file allocation only works for local files, so may fail + * for files on network shares */ + int rallocation = hypervGetFileSize(priv, diskpath, &allocation); + + /* if both queries were unsuccessful, just return an error */ + if (rcapacity < 0 && rallocation < 0) { + virReportError(VIR_ERR_INTERNAL_ERROR, _("Unable to get info for disk '%s'"), diskpath); + return -1; + } + + /* if we failed to get the capacity from the ImageManagementService (i.e. + * the disk path wasn't a vhd file), just use the file size */ + if (capacity == 0) + capacity = allocation; + + info->capacity = capacity; + info->physical = info->allocation = allocation; + return 0; +} + +static int +hypervDomainGetBlockInfo(virDomainPtr domain, + const char *path, + virDomainBlockInfoPtr info, + unsigned int flags) +{ + hypervPrivate *priv = domain->conn->privateData; + char uuid_string[VIR_UUID_STRING_BUFLEN]; + g_autoptr(Msvm_ResourceAllocationSettingData) resource_settings = NULL; + g_autoptr(Msvm_StorageAllocationSettingData) storage_settings = NULL; + g_autoptr(Msvm_VirtualSystemSettingData) system_settings = NULL; + g_autoptr(virDomainDef) def = NULL; + virDomainDiskDef *disk = NULL; + const char *diskpath = NULL; + + virCheckFlags(0, -1); + + virUUIDFormat(domain->uuid, uuid_string); + + if (hypervGetMsvmVirtualSystemSettingDataFromUUID(priv, uuid_string, &system_settings) < 0) { + virReportError(VIR_ERR_NO_DOMAIN, _("No domain with UUID %1$s"), uuid_string); + return -1; + } + + if (hypervGetResourceAllocationSD(priv, + system_settings->data->InstanceID, + &resource_settings) < 0) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("Unable to get resource allocation settings data")); + return -1; + } + + if (hypervGetStorageAllocationSD(priv, + system_settings->data->InstanceID, + &storage_settings) < 0) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("Unable to get storage allocation settings data")); + return -1; + } + + if (!(def = virDomainDefNew(priv->xmlopt))) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("Failed to create a new virDomainDef")); + return -1; + } + + /* Process storage and resources to get disk names */ + if (hypervDomainDefParseStorage(priv, def, resource_settings, storage_settings) < 0) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("Failed to parse storage")); + return -1; + } + + disk = virDomainDiskByName(def, path, false); + if (!disk) { + virReportError(VIR_ERR_INVALID_ARG, + _("invalid path %1$s not assigned to domain"), path); + return -1; + } + + diskpath = virDomainDiskGetSource(disk); + if (!diskpath) { + virReportError(VIR_ERR_OPERATION_UNSUPPORTED, + _("disk '%1$s' has no source path"), path); + return -1; + } + + if (virDomainDiskGetType(disk) == VIR_STORAGE_TYPE_BLOCK) { + unsigned int driveNumber = 0; + g_autoptr(Msvm_DiskDrive) diskdrive = NULL; + + /* BLOCK type disks have their source path set to the windows drive number */ + if (virStrToLong_ui(diskpath, NULL, 10, &driveNumber) < 0) { + virReportError(VIR_ERR_INTERNAL_ERROR, + _("Invalid drive number '%1$s' for physical disk"), diskpath); + return -1; + } + + if (hypervGetPhysicalDiskBlockInfo(priv, driveNumber, info) < 0) + return -1; + } else if (virDomainDiskGetType(disk) == VIR_STORAGE_TYPE_FILE) { + /* first try querying the disk via the image management service which supports .vhd(x) files */ + if (hypervGetVirtualDiskBlockInfo(priv, diskpath, info) < 0) + return -1; + } else { + virReportError(VIR_ERR_OPERATION_UNSUPPORTED, + _("Unsupported disk type %1$d for disk '%2$s'"), + virDomainDiskGetType(disk), path); + return -1; + } + + return 0; +} + + static virHypervisorDriver hypervHypervisorDriver = { .name = "Hyper-V", .connectOpen = hypervConnectOpen, /* 0.9.5 */ @@ -3886,6 +4090,7 @@ static virHypervisorDriver hypervHypervisorDriver = { .domainSendKey = hypervDomainSendKey, /* 3.6.0 */ .connectIsAlive = hypervConnectIsAlive, /* 0.9.8 */ .domainInterfaceAddresses = hypervDomainInterfaceAddresses, /* 12.1.0 */ + .domainGetBlockInfo = hypervDomainGetBlockInfo, /* 12.1.0 */ }; diff --git a/src/hyperv/hyperv_wmi.c b/src/hyperv/hyperv_wmi.c index eee42b5c70..90bbc42b6d 100644 --- a/src/hyperv/hyperv_wmi.c +++ b/src/hyperv/hyperv_wmi.c @@ -31,6 +31,7 @@ #include <u/syslog.h> #include "internal.h" +#include "libvirt/virterror.h" #include "virerror.h" #include "datatypes.h" #include "viralloc.h" @@ -1507,6 +1508,46 @@ hypervGetEthernetPortAllocationSD(hypervPrivate *priv, } +int +hypervImageManagementServiceGetVHDSD(hypervPrivate *priv, + const char *vhdPath, + WsXmlDocH *settingDataDoc) +{ + hypervInvokeParamsList *params = NULL; + g_auto(WsXmlDocH) response = NULL; + g_autofree char *settingDataXmlStr = NULL; + + params = hypervCreateInvokeParamsList("GetVirtualHardDiskSettingData", + MSVM_IMAGEMANAGEMENTSERVICE_SELECTOR, + Msvm_ImageManagementService_WmiInfo); + if (hypervAddSimpleParam(params, "Path", vhdPath) < 0) + return -1; + + if (hypervInvokeMethod(priv, ¶ms, &response) < 0) + return -1; + + settingDataXmlStr = ws_xml_get_xpath_value(response, + (char *)"/s:Envelope/s:Body/p:GetVirtualHardDiskSettingData_OUTPUT/p:SettingData"); + + if (!settingDataXmlStr) { + virReportError(VIR_ERR_INTERNAL_ERROR, + _("Could not extract SettingData from response for '%1$s'"), vhdPath); + return -1; + } + + /* the method returns an embedded CIM-XML document as a string, so we need + * to parse it as xml */ + *settingDataDoc = ws_xml_read_memory(settingDataXmlStr, strlen(settingDataXmlStr), "UTF-8", 0); + if (!*settingDataDoc) { + virReportError(VIR_ERR_INTERNAL_ERROR, + _("Could not parse VHD SettingData XML for '%1$s'"), vhdPath); + return -1; + } + + return 0; +} + + /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * Msvm_VirtualSystemManagementService */ diff --git a/src/hyperv/hyperv_wmi.h b/src/hyperv/hyperv_wmi.h index 9093aec455..11d61edfc5 100644 --- a/src/hyperv/hyperv_wmi.h +++ b/src/hyperv/hyperv_wmi.h @@ -36,6 +36,9 @@ #define MSVM_VIRTUALSYSTEMMANAGEMENTSERVICE_SELECTOR \ "CreationClassName=Msvm_VirtualSystemManagementService" +#define MSVM_IMAGEMANAGEMENTSERVICE_SELECTOR \ + "CreationClassName=Msvm_ImageManagementService" + int hypervVerifyResponse(WsManClient *client, WsXmlDocH response, const char *detail); @@ -263,6 +266,10 @@ int hypervGetEthernetPortAllocationSD(hypervPrivate *priv, const char *id, Msvm_EthernetPortAllocationSettingData **data); +int hypervImageManagementServiceGetVHDSD(hypervPrivate *priv, + const char *vhdPath, + WsXmlDocH *settingDataDoc); + /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * Msvm_VirtualSystemManagementService */ diff --git a/src/hyperv/hyperv_wmi_generator.input b/src/hyperv/hyperv_wmi_generator.input index e6053267f8..017b7a0fa5 100644 --- a/src/hyperv/hyperv_wmi_generator.input +++ b/src/hyperv/hyperv_wmi_generator.input @@ -1143,3 +1143,112 @@ class Msvm_VideoHead uint32 CurrentNumberOfColumns uint64 CurrentNumberOfColors end + + +class Msvm_ImageManagementService + 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_VirtualHardDiskSettingData + string InstanceID + string Caption + string Description + string ElementName + uint16 Type + uint16 Format + string Path + string ParentPath + datetime ParentTimestamp + string ParentIdentifier + uint64 MaxInternalSize + uint32 BlockSize + uint32 LogicalSectorSize + uint32 PhysicalSectorSize + string VirtualDiskId + uint64 DataAlignment + uint16 PmemAddressAbstractionType + boolean IsPmemCompatible +end + + +class Win32_DiskDrive + uint16 Availability + uint32 BytesPerSector + uint16 Capabilities[] + string CapabilityDescriptions[] + string Caption + string CompressionMethod + uint32 ConfigManagerErrorCode + boolean ConfigManagerUserConfig + string CreationClassName + uint64 DefaultBlockSize + string Description + string DeviceID + boolean ErrorCleared + string ErrorDescription + string ErrorMethodology + string FirmwareRevision + uint32 Index + datetime InstallDate + string InterfaceType + uint32 LastErrorCode + string Manufacturer + uint64 MaxBlockSize + uint64 MaxMediaSize + boolean MediaLoaded + string MediaType + uint64 MinBlockSize + string Model + string Name + boolean NeedsCleaning + uint32 NumberOfMediaSupported + uint32 Partitions + string PNPDeviceID + uint16 PowerManagementCapabilities[] + boolean PowerManagementSupported + uint32 SCSIBus + uint16 SCSILogicalUnit + uint16 SCSIPort + uint16 SCSITargetId + uint32 SectorsPerTrack + string SerialNumber + uint32 Signature + uint64 Size + string Status + uint16 StatusInfo + string SystemCreationClassName + string SystemName + uint64 TotalCylinders + uint32 TotalHeads + uint64 TotalSectors + uint64 TotalTracks + uint32 TracksPerCylinder +end -- 2.53.0