hyperv: XML parsing of storage volumes

dumpxml can now serialize:
* floppy drives
* file-backed and device-backed disk drives
* images mounted to virtual CD/DVD drives
* IDE and SCSI controllers

Reviewed-by: Daniel P. Berrangé <berrange@redhat.com>
Co-authored-by: Sri Ramanujam <sramanujam@datto.com>
Signed-off-by: Matt Coleman <matt@datto.com>
This commit is contained in:
Matt Coleman 2020-11-23 12:39:52 -05:00 committed by Daniel P. Berrangé
parent 5245a7ae4c
commit a7a1d1f59e
7 changed files with 637 additions and 1 deletions

View File

@ -293,6 +293,400 @@ hypervCapsInit(hypervPrivate *priv)
return NULL;
}
/*
* Virtual device functions
*/
static int
hypervGetDeviceParentRasdFromDeviceId(const char *parentDeviceId,
Msvm_ResourceAllocationSettingData *list,
Msvm_ResourceAllocationSettingData **out)
{
Msvm_ResourceAllocationSettingData *entry = list;
*out = NULL;
while (entry) {
g_autofree char *escapedDeviceId = virStringReplace(entry->data->InstanceID, "\\", "\\\\");
g_autofree char *expectedSuffix = g_strdup_printf("%s\"", escapedDeviceId);
if (g_str_has_suffix(parentDeviceId, expectedSuffix)) {
*out = entry;
break;
}
entry = entry->next;
}
if (*out)
return 0;
virReportError(VIR_ERR_INTERNAL_ERROR,
_("Failed to locate parent device with ID '%s'"),
parentDeviceId);
return -1;
}
/*
* Functions for deserializing device entries
*/
static int
hypervDomainDefAppendController(virDomainDefPtr def,
int idx,
virDomainControllerType controllerType)
{
virDomainControllerDefPtr controller = NULL;
if (!(controller = virDomainControllerDefNew(controllerType)))
return -1;
controller->idx = idx;
if (VIR_APPEND_ELEMENT(def->controllers, def->ncontrollers, controller) < 0)
return -1;
return 0;
}
static int
hypervDomainDefAppendIDEController(virDomainDefPtr def)
{
return hypervDomainDefAppendController(def, 0, VIR_DOMAIN_CONTROLLER_TYPE_IDE);
}
static int
hypervDomainDefAppendSCSIController(virDomainDefPtr def, int idx)
{
return hypervDomainDefAppendController(def, idx, VIR_DOMAIN_CONTROLLER_TYPE_SCSI);
}
static int
hypervDomainDefAppendDisk(virDomainDefPtr def,
virDomainDiskDefPtr disk,
virDomainDiskBus busType,
int diskNameOffset,
const char *diskNamePrefix,
int maxControllers,
Msvm_ResourceAllocationSettingData **controllers,
Msvm_ResourceAllocationSettingData *diskParent,
Msvm_ResourceAllocationSettingData *diskController)
{
size_t i = 0;
int ctrlr_idx = -1;
int addr = -1;
if (virStrToLong_i(diskParent->data->AddressOnParent, NULL, 10, &addr) < 0)
return -1;
if (addr < 0)
return -1;
/* Find controller index */
for (i = 0; i < maxControllers; i++) {
if (diskController == controllers[i]) {
ctrlr_idx = i;
break;
}
}
if (ctrlr_idx < 0) {
virReportError(VIR_ERR_INTERNAL_ERROR, "%s", _("Could not find controller for disk!"));
return -1;
}
disk->bus = busType;
disk->dst = virIndexToDiskName(ctrlr_idx * diskNameOffset + addr, diskNamePrefix);
if (busType == VIR_DOMAIN_DISK_BUS_IDE) {
disk->info.addr.drive.controller = 0;
disk->info.addr.drive.bus = ctrlr_idx;
} else {
disk->info.addr.drive.controller = ctrlr_idx;
disk->info.addr.drive.bus = 0;
}
disk->info.addr.drive.target = 0;
disk->info.addr.drive.unit = addr;
if (VIR_APPEND_ELEMENT(def->disks, def->ndisks, disk) < 0)
return -1;
return 0;
}
static int
hypervDomainDefParseFloppyStorageExtent(virDomainDefPtr def, virDomainDiskDefPtr disk)
{
disk->bus = VIR_DOMAIN_DISK_BUS_FDC;
disk->dst = g_strdup("fda");
if (VIR_APPEND_ELEMENT(def->disks, def->ndisks, disk) < 0)
return -1;
return 0;
}
static int
hypervDomainDefParseVirtualExtent(hypervPrivate *priv,
virDomainDefPtr def,
Msvm_StorageAllocationSettingData *disk_entry,
Msvm_ResourceAllocationSettingData *rasd,
Msvm_ResourceAllocationSettingData **ideChannels,
Msvm_ResourceAllocationSettingData **scsiControllers)
{
Msvm_ResourceAllocationSettingData *diskParent = NULL;
Msvm_ResourceAllocationSettingData *controller = NULL;
virDomainDiskDefPtr disk = NULL;
int result = -1;
if (disk_entry->data->HostResource.count < 1)
goto cleanup;
if (!(disk = virDomainDiskDefNew(priv->xmlopt))) {
virReportError(VIR_ERR_INTERNAL_ERROR, "%s", _("Could not allocate disk definition"));
goto cleanup;
}
/* get disk associated with storage extent */
if (hypervGetDeviceParentRasdFromDeviceId(disk_entry->data->Parent, rasd, &diskParent) < 0)
goto cleanup;
/* get associated controller */
if (hypervGetDeviceParentRasdFromDeviceId(diskParent->data->Parent, rasd, &controller) < 0)
goto cleanup;
/* common fields first */
disk->src->type = VIR_STORAGE_TYPE_FILE;
disk->info.type = VIR_DOMAIN_DEVICE_ADDRESS_TYPE_DRIVE;
/* note if it's a CDROM disk */
if (STREQ(disk_entry->data->ResourceSubType, "Microsoft:Hyper-V:Virtual CD/DVD Disk"))
disk->device = VIR_DOMAIN_DISK_DEVICE_CDROM;
else
disk->device = VIR_DOMAIN_DISK_DEVICE_DISK;
/* copy in the source path */
virDomainDiskSetSource(disk, *(char **)disk_entry->data->HostResource.data);
/* controller-specific fields */
if (controller->data->ResourceType == MSVM_RASD_RESOURCETYPE_PARALLEL_SCSI_HBA) {
if (hypervDomainDefAppendDisk(def, disk, VIR_DOMAIN_DISK_BUS_SCSI,
64, "sd", HYPERV_MAX_SCSI_CONTROLLERS,
scsiControllers, diskParent, controller) < 0) {
goto cleanup;
}
} else if (controller->data->ResourceType == MSVM_RASD_RESOURCETYPE_IDE_CONTROLLER) {
if (hypervDomainDefAppendDisk(def, disk, VIR_DOMAIN_DISK_BUS_IDE,
2, "hd", HYPERV_MAX_IDE_CHANNELS,
ideChannels, diskParent, controller) < 0) {
goto cleanup;
}
} else if (controller->data->ResourceType == MSVM_RASD_RESOURCETYPE_OTHER &&
diskParent->data->ResourceType == MSVM_RASD_RESOURCETYPE_DISKETTE_DRIVE) {
if (hypervDomainDefParseFloppyStorageExtent(def, disk) < 0)
goto cleanup;
disk->device = VIR_DOMAIN_DISK_DEVICE_FLOPPY;
} else {
virReportError(VIR_ERR_INTERNAL_ERROR,
_("Unrecognized controller type %d"),
controller->data->ResourceType);
goto cleanup;
}
result = 0;
cleanup:
if (result != 0 && disk)
virDomainDiskDefFree(disk);
return result;
}
static int
hypervDomainDefParsePhysicalDisk(hypervPrivate *priv,
virDomainDefPtr def,
Msvm_ResourceAllocationSettingData *entry,
Msvm_ResourceAllocationSettingData *rasd,
Msvm_ResourceAllocationSettingData **ideChannels,
Msvm_ResourceAllocationSettingData **scsiControllers)
{
int result = -1;
Msvm_ResourceAllocationSettingData *controller = NULL;
Msvm_DiskDrive *diskdrive = NULL;
virDomainDiskDefPtr disk = NULL;
char **hostResource = entry->data->HostResource.data;
g_autofree char *hostEscaped = NULL;
g_autofree char *driveNumberStr = NULL;
g_auto(virBuffer) query = VIR_BUFFER_INITIALIZER;
int addr = -1, ctrlr_idx = -1;
size_t i = 0;
if (virStrToLong_i(entry->data->AddressOnParent, NULL, 10, &addr) < 0)
return -1;
if (addr < 0)
return -1;
if (hypervGetDeviceParentRasdFromDeviceId(entry->data->Parent, rasd, &controller) < 0)
goto cleanup;
/* create disk definition */
if (!(disk = virDomainDiskDefNew(priv->xmlopt))) {
virReportError(VIR_ERR_INTERNAL_ERROR, "%s", _("Could not allocate disk def"));
goto cleanup;
}
/* Query Msvm_DiskDrive for the DriveNumber */
hostEscaped = virStringReplace(*hostResource, "\\\"", "\"");
hostEscaped = virStringReplace(hostEscaped, "\\", "\\\\");
/* quotes must be preserved, so virBufferEscapeSQL can't be used */
virBufferAsprintf(&query,
MSVM_DISKDRIVE_WQL_SELECT "WHERE __PATH='%s'",
hostEscaped);
if (hypervGetWmiClass(Msvm_DiskDrive, &diskdrive) < 0)
goto cleanup;
if (!diskdrive) {
virReportError(VIR_ERR_INTERNAL_ERROR, "%s", _("Could not find Msvm_DiskDrive object"));
goto cleanup;
}
driveNumberStr = g_strdup_printf("%u", diskdrive->data->DriveNumber);
virDomainDiskSetSource(disk, driveNumberStr);
if (addr < 0)
goto cleanup;
if (controller->data->ResourceType == MSVM_RASD_RESOURCETYPE_PARALLEL_SCSI_HBA) {
for (i = 0; i < HYPERV_MAX_SCSI_CONTROLLERS; i++) {
if (controller == scsiControllers[i]) {
ctrlr_idx = i;
break;
}
}
disk->bus = VIR_DOMAIN_DISK_BUS_SCSI;
disk->dst = virIndexToDiskName(ctrlr_idx * 64 + addr, "sd");
disk->info.addr.drive.unit = addr;
disk->info.addr.drive.controller = ctrlr_idx;
disk->info.addr.drive.bus = 0;
} else if (controller->data->ResourceType == MSVM_RASD_RESOURCETYPE_IDE_CONTROLLER) {
for (i = 0; i < HYPERV_MAX_IDE_CHANNELS; i++) {
if (controller == ideChannels[i]) {
ctrlr_idx = i;
break;
}
}
disk->bus = VIR_DOMAIN_DISK_BUS_IDE;
disk->dst = virIndexToDiskName(ctrlr_idx * 4 + addr, "hd");
disk->info.addr.drive.unit = addr;
disk->info.addr.drive.controller = 0;
disk->info.addr.drive.bus = ctrlr_idx;
} else {
virReportError(VIR_ERR_INTERNAL_ERROR, "%s", _("Invalid controller type for LUN"));
goto cleanup;
}
disk->info.addr.drive.target = 0;
virDomainDiskSetType(disk, VIR_STORAGE_TYPE_BLOCK);
disk->device = VIR_DOMAIN_DISK_DEVICE_DISK;
disk->info.type = VIR_DOMAIN_DEVICE_ADDRESS_TYPE_DRIVE;
if (VIR_APPEND_ELEMENT(def->disks, def->ndisks, disk) < 0)
goto cleanup;
result = 0;
cleanup:
if (result != 0 && disk)
virDomainDiskDefFree(disk);
hypervFreeObject(priv, (hypervObject *)diskdrive);
return result;
}
static int
hypervDomainDefParseStorage(hypervPrivate *priv,
virDomainDefPtr def,
Msvm_ResourceAllocationSettingData *rasd,
Msvm_StorageAllocationSettingData *sasd)
{
Msvm_ResourceAllocationSettingData *entry = rasd;
Msvm_StorageAllocationSettingData *disk_entry = sasd;
Msvm_ResourceAllocationSettingData *ideChannels[HYPERV_MAX_IDE_CHANNELS];
Msvm_ResourceAllocationSettingData *scsiControllers[HYPERV_MAX_SCSI_CONTROLLERS];
bool hasIdeController = false;
int channel = -1;
int scsi_idx = 0;
/* first pass: populate storage controllers */
while (entry) {
if (entry->data->ResourceType == MSVM_RASD_RESOURCETYPE_IDE_CONTROLLER) {
channel = entry->data->Address[0] - '0';
ideChannels[channel] = entry;
if (!hasIdeController) {
/* Hyper-V represents its PIIX4 controller's two channels as separate objects. */
if (hypervDomainDefAppendIDEController(def) < 0) {
virReportError(VIR_ERR_INTERNAL_ERROR, "%s", _("Could not add IDE controller"));
return -1;
}
hasIdeController = true;
}
} else if (entry->data->ResourceType == MSVM_RASD_RESOURCETYPE_PARALLEL_SCSI_HBA) {
scsiControllers[scsi_idx++] = entry;
if (hypervDomainDefAppendSCSIController(def, scsi_idx - 1) < 0) {
virReportError(VIR_ERR_INTERNAL_ERROR, "%s", _("Could not parse SCSI controller"));
return -1;
}
}
entry = entry->next;
}
/* second pass: populate physical disks */
entry = rasd;
while (entry) {
if (entry->data->ResourceType == MSVM_RASD_RESOURCETYPE_DISK_DRIVE &&
entry->data->HostResource.count > 0) {
char **hostResource = entry->data->HostResource.data;
if (strstr(*hostResource, "NODRIVE")) {
/* Hyper-V doesn't let you define LUNs with no connection */
VIR_DEBUG("Skipping empty LUN '%s'", *hostResource);
entry = entry->next;
continue;
}
if (hypervDomainDefParsePhysicalDisk(priv, def, entry, rasd,
ideChannels, scsiControllers) < 0)
return -1;
}
entry = entry->next;
}
/* third pass: populate virtual disks */
while (disk_entry) {
if (hypervDomainDefParseVirtualExtent(priv, def, disk_entry, rasd,
ideChannels, scsiControllers) < 0)
return -1;
disk_entry = disk_entry->next;
}
return 0;
}
/*
* Driver functions
*/
@ -1249,6 +1643,8 @@ hypervDomainGetXMLDesc(virDomainPtr domain, unsigned int flags)
Msvm_VirtualSystemSettingData *virtualSystemSettingData = NULL;
Msvm_ProcessorSettingData *processorSettingData = NULL;
Msvm_MemorySettingData *memorySettingData = NULL;
Msvm_ResourceAllocationSettingData *rasd = NULL;
Msvm_StorageAllocationSettingData *sasd = NULL;
virCheckFlags(VIR_DOMAIN_XML_COMMON_FLAGS, NULL);
@ -1275,6 +1671,18 @@ hypervDomainGetXMLDesc(virDomainPtr domain, unsigned int flags)
&memorySettingData) < 0)
goto cleanup;
if (hypervGetResourceAllocationSD(priv,
virtualSystemSettingData->data->InstanceID,
&rasd) < 0) {
goto cleanup;
}
if (hypervGetStorageAllocationSD(priv,
virtualSystemSettingData->data->InstanceID,
&sasd) < 0) {
goto cleanup;
}
/* Fill struct */
def->virtType = VIR_DOMAIN_VIRT_HYPERV;
@ -1324,7 +1732,20 @@ hypervDomainGetXMLDesc(virDomainPtr domain, unsigned int flags)
def->os.type = VIR_DOMAIN_OSTYPE_HVM;
/* FIXME: devices section is totally missing */
/* Allocate space for all potential devices */
/* 256 scsi drives + 4 ide drives */
def->disks = g_new0(virDomainDiskDefPtr,
HYPERV_MAX_SCSI_CONTROLLERS * HYPERV_MAX_DRIVES_PER_SCSI_CONTROLLER +
HYPERV_MAX_IDE_CHANNELS * HYPERV_MAX_DRIVES_PER_IDE_CHANNEL);
def->ndisks = 0;
/* 1 ide & 4 scsi controllers */
def->controllers = g_new0(virDomainControllerDefPtr, 5);
def->ncontrollers = 0;
if (hypervDomainDefParseStorage(priv, def, rasd, sasd) < 0)
goto cleanup;
/* XXX xmlopts must be non-NULL */
xml = virDomainDefFormat(def, NULL,
@ -1336,6 +1757,8 @@ hypervDomainGetXMLDesc(virDomainPtr domain, unsigned int flags)
hypervFreeObject(priv, (hypervObject *)virtualSystemSettingData);
hypervFreeObject(priv, (hypervObject *)processorSettingData);
hypervFreeObject(priv, (hypervObject *)memorySettingData);
hypervFreeObject(priv, (hypervObject *)rasd);
hypervFreeObject(priv, (hypervObject *)sasd);
return xml;
}

View File

@ -22,4 +22,9 @@
#pragma once
#define HYPERV_MAX_SCSI_CONTROLLERS 4
#define HYPERV_MAX_DRIVES_PER_SCSI_CONTROLLER 64
#define HYPERV_MAX_IDE_CHANNELS 2
#define HYPERV_MAX_DRIVES_PER_IDE_CHANNEL 2
int hypervRegister(void);

View File

@ -28,10 +28,12 @@
#include "virerror.h"
#include "hyperv_util.h"
#include "capabilities.h"
#include "domain_conf.h"
typedef struct _hypervPrivate hypervPrivate;
struct _hypervPrivate {
hypervParsedUri *parsedUri;
WsManClient *client;
virCapsPtr caps;
virDomainXMLOptionPtr xmlopt;
};

View File

@ -1490,6 +1490,32 @@ hypervGetMsvmVirtualSystemSettingDataFromUUID(hypervPrivate *priv,
}
int
hypervGetResourceAllocationSD(hypervPrivate *priv,
const char *id,
Msvm_ResourceAllocationSettingData **data)
{
g_auto(virBuffer) query = VIR_BUFFER_INITIALIZER;
virBufferEscapeSQL(&query,
"ASSOCIATORS OF {Msvm_VirtualSystemSettingData.InstanceID='%s'} "
"WHERE AssocClass = Msvm_VirtualSystemSettingDataComponent "
"ResultClass = Msvm_ResourceAllocationSettingData",
id);
if (hypervGetWmiClass(Msvm_ResourceAllocationSettingData, data) < 0)
return -1;
if (!*data) {
virReportError(VIR_ERR_INTERNAL_ERROR,
_("Could not look up resource allocation setting data with virtual system instance ID '%s'"),
id);
return -1;
}
return 0;
}
int
hypervGetProcessorSD(hypervPrivate *priv,
const char *id,
@ -1536,6 +1562,25 @@ hypervGetMemorySD(hypervPrivate *priv,
}
int
hypervGetStorageAllocationSD(hypervPrivate *priv,
const char *id,
Msvm_StorageAllocationSettingData **data)
{
g_auto(virBuffer) query = VIR_BUFFER_INITIALIZER;
virBufferEscapeSQL(&query,
"ASSOCIATORS OF {Msvm_VirtualSystemSettingData.InstanceID='%s'} "
"WHERE AssocClass = Msvm_VirtualSystemSettingDataComponent "
"ResultClass = Msvm_StorageAllocationSettingData",
id);
if (hypervGetWmiClass(Msvm_StorageAllocationSettingData, data) < 0)
return -1;
return 0;
}
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* Msvm_VirtualSystemManagementService
*/

View File

@ -236,6 +236,10 @@ int hypervGetMsvmVirtualSystemSettingDataFromUUID(hypervPrivate *priv,
const char *uuid_string,
Msvm_VirtualSystemSettingData **list);
int hypervGetResourceAllocationSD(hypervPrivate *priv,
const char *id,
Msvm_ResourceAllocationSettingData **data);
int hypervGetProcessorSD(hypervPrivate *priv,
const char *id,
Msvm_ProcessorSettingData **data);
@ -244,6 +248,10 @@ int hypervGetMemorySD(hypervPrivate *priv,
const char *vssd_instanceid,
Msvm_MemorySettingData **list);
int hypervGetStorageAllocationSD(hypervPrivate *priv,
const char *id,
Msvm_StorageAllocationSettingData **data);
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* Msvm_VirtualSystemManagementService
*/

View File

@ -98,6 +98,25 @@ enum _Msvm_ConcreteJob_JobState {
};
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* Msvm_ResourceAllocationSettingData
*/
/* https://docs.microsoft.com/en-us/windows/win32/hyperv_v2/msvm-resourceallocationsettingdata */
enum _Msvm_ResourceAllocationSettingData_ResourceType {
MSVM_RASD_RESOURCETYPE_OTHER = 1,
MSVM_RASD_RESOURCETYPE_IDE_CONTROLLER = 5,
MSVM_RASD_RESOURCETYPE_PARALLEL_SCSI_HBA = 6,
MSVM_RASD_RESOURCETYPE_DISKETTE_DRIVE = 14,
MSVM_RASD_RESOURCETYPE_CD_DRIVE = 15,
MSVM_RASD_RESOURCETYPE_DVD_DRIVE = 16,
MSVM_RASD_RESOURCETYPE_DISK_DRIVE = 17,
MSVM_RASD_RESOURCETYPE_STORAGE_EXTENT = 19,
};
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* WMI
*/

View File

@ -601,6 +601,34 @@ class Msvm_VirtualSystemManagementService
end
class Msvm_ResourceAllocationSettingData
string InstanceID
string Caption
string Description
string ElementName
uint16 ResourceType
string OtherResourceType
string ResourceSubType
string PoolID
uint16 ConsumerVisibility
string HostResource[]
string AllocationUnits
uint64 VirtualQuantity
uint64 Reservation
uint64 Limit
uint32 Weight
boolean AutomaticAllocation
boolean AutomaticDeallocation
string Parent
string Connection[]
string Address
uint16 MappingBehavior
string AddressOnParent
string VirtualQuantityUnits
string VirtualSystemIdentifiers[]
end
class Msvm_Keyboard
string InstanceID
string Caption
@ -688,3 +716,109 @@ class Msvm_ShutdownComponent
uint16 AdditionalAvailability[]
uint64 MaxQuiesceTime
end
class Msvm_DiskDrive
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 DeviceID
boolean PowerManagementSupported
uint16 PowerManagementCapabilities[]
uint16 Availability
uint16 StatusInfo
uint32 LastErrorCode
string ErrorDescription
boolean ErrorCleared
string OtherIdentifyingInfo[]
uint64 PowerOnHours
uint64 TotalPowerOnHours
string IdentifyingDescriptions[]
uint16 AdditionalAvailability[]
uint64 MaxQuiesceTime
uint16 Capabilities[]
string CapabilityDescriptions[]
string ErrorMethodology
string CompressionMethod
uint32 NumberOfMediaSupported
uint64 MaxMediaSize
uint64 DefaultBlockSize
uint64 MaxBlockSize
uint64 MinBlockSize
boolean NeedsCleaning
boolean MediaIsLocked
uint16 Security
datetime LastCleaned
uint64 MaxAccessTime
uint32 UncompressedDataRate
uint64 LoadTime
uint64 UnloadTime
uint64 MountCount
datetime TimeOfLastMount
uint64 TotalMountTime
string UnitsDescription
uint64 MaxUnitsBeforeCleaning
uint64 UnitsUsed
uint32 DriveNumber
end
class Msvm_StorageAllocationSettingData
string InstanceID
string Caption
string Description
string ElementName
uint16 ResourceType
string OtherResourceType
string ResourceSubType
string PoolID
uint16 ConsumerVisibility
string HostResource[]
string AllocationUnits
uint64 VirtualQuantity
uint64 Limit
uint32 Weight
boolean AutomaticAllocation
boolean AutomaticDeallocation
string Parent
string Connection[]
string Address
uint16 MappingBehavior
string AddressOnParent
uint64 VirtualResourceBlockSize
string VirtualQuantityUnits
uint16 Access
uint64 HostResourceBlockSize
uint64 Reservation
uint64 HostExtentStartingAddress
string HostExtentName
uint16 HostExtentNameFormat
string OtherHostExtentNameFormat
uint16 HostExtentNameNamespace
string OtherHostExtentNameNamespace
uint64 IOPSLimit
uint64 IOPSReservation
string IOPSAllocationUnits
boolean PersistentReservationsSupported
end