From: Martin Kletzander <mkletzan(a)redhat.com>
NVMe disks are essentially a namespace of an NVMe controller, but to
make it easier for the users to just add a disk, the necessary details
like adding the proper controller, setting the serial number for the
controller based on the disk, are done automatically.
Signed-off-by: Martin Kletzander <mkletzan(a)redhat.com>
Signed-off-by: Honglei Wang <honglei.wang(a)smartx.com>
---
docs/formatdomain.rst | 9 ++-
src/conf/domain_conf.c | 63 +++++++++++++++++--
src/conf/domain_conf.h | 1 +
src/conf/domain_postparse.c | 2 +
src/conf/domain_validate.c | 4 +-
src/conf/schemas/domaincommon.rng | 12 +++-
src/hyperv/hyperv_driver.c | 2 +
src/qemu/qemu_alias.c | 1 +
src/qemu/qemu_command.c | 2 +
src/qemu/qemu_domain_address.c | 1 +
src/qemu/qemu_hotplug.c | 2 +
src/qemu/qemu_validate.c | 7 ++-
src/test/test_driver.c | 2 +
src/vbox/vbox_common.c | 1 +
src/vmx/vmx.c | 1 +
.../disk-nvme-invalid-serials.xml | 29 +++++++++
tests/genericxml2xmlindata/disk-nvme.xml | 32 ++++++++++
tests/genericxml2xmloutdata/disk-nvme.xml | 38 +++++++++++
tests/genericxml2xmltest.c | 2 +
19 files changed, 199 insertions(+), 12 deletions(-)
create mode 100644 tests/genericxml2xmlindata/disk-nvme-invalid-serials.xml
create mode 100644 tests/genericxml2xmlindata/disk-nvme.xml
create mode 100644 tests/genericxml2xmloutdata/disk-nvme.xml
diff --git a/docs/formatdomain.rst b/docs/formatdomain.rst
index fdc90c61f86d..9f3d09ba1e32 100644
--- a/docs/formatdomain.rst
+++ b/docs/formatdomain.rst
@@ -2824,7 +2824,7 @@ paravirtualized driver is specified via the ``disk`` element.
<disk type='file' device='disk'>
<driver name='qemu' type='qcow2' />
<source file='/var/lib/libvirt/images/disk.qcow2'/>
- <target dev='vdh' bus='virtio'/>
+ <target dev='nvme0n1' bus='nvme'/>
<throttlefilters>
<throttlefilter group='limit2'/>
<throttlefilter group='limit012'/>
@@ -3313,7 +3313,8 @@ paravirtualized driver is specified via the ``disk`` element.
name in the guest OS. Treat it as a device ordering hint. The optional
``bus`` attribute specifies the type of disk device to emulate; possible
values are driver specific, with typical values being "ide",
"scsi",
- "virtio", "xen", "usb", "sata", or
"sd" :since:`"sd" since 1.1.2`. If
+ "virtio", "xen", "usb", "sata",
"sd", or "nvme"
+ :since:`"sd" since 1.1.2, "nvme" since 11.5.0`. If
omitted, the bus type is inferred from the style of the device name (e.g. a
device named 'sda' will typically be exported using a SCSI bus). The optional
attribute ``tray`` indicates the tray status of the removable disks (i.e.
@@ -3651,7 +3652,9 @@ paravirtualized driver is specified via the ``disk`` element.
If present, this specify serial number of virtual hard drive. For example, it
may look like ``<serial>WD-WMAP9A966149</serial>``. Not supported for
scsi-block devices, that is those using disk ``type`` 'block' using
- ``device`` 'lun' on ``bus`` 'scsi'. :since:`Since 0.7.1`
+ ``device`` 'lun' on ``bus`` 'scsi'. Also not supported for multiple
NVMe
+ devices on the same controller since those have serial number per controller
+ and not per disk. :since:`Since 0.7.1`
Note that depending on hypervisor and device type the serial number may be
truncated silently. IDE/SATA devices are commonly limited to 20 characters.
diff --git a/src/conf/domain_conf.c b/src/conf/domain_conf.c
index c046f5e7cbe5..f3a118e8fc8a 100644
--- a/src/conf/domain_conf.c
+++ b/src/conf/domain_conf.c
@@ -372,6 +372,7 @@ VIR_ENUM_IMPL(virDomainDiskBus,
"uml",
"sata",
"sd",
+ "nvme",
);
VIR_ENUM_IMPL(virDomainDiskCache,
@@ -6813,8 +6814,10 @@ virDomainDiskDefAssignAddress(virDomainXMLOption *xmlopt
G_GNUC_UNUSED,
virDomainDiskDef *def,
const virDomainDef *vmdef)
{
- int idx = virDiskNameToIndex(def->dst);
- if (idx < 0) {
+ int idx = 0;
+ int nvme_ctrl = 0;
+
+ if (virDiskNameParse(def->dst, &nvme_ctrl, &idx, NULL) < 0) {
virReportError(VIR_ERR_XML_ERROR,
_("Unknown disk name '%1$s' and no address
specified"),
def->dst);
@@ -6891,6 +6894,13 @@ virDomainDiskDefAssignAddress(virDomainXMLOption *xmlopt
G_GNUC_UNUSED,
def->info.addr.drive.unit = idx % 2;
break;
+ case VIR_DOMAIN_DISK_BUS_NVME:
+ def->info.type = VIR_DOMAIN_DEVICE_ADDRESS_TYPE_DRIVE;
+ def->info.addr.drive.controller = nvme_ctrl;
+ def->info.addr.drive.bus = 0;
+ def->info.addr.drive.unit = idx;
+ break;
+
case VIR_DOMAIN_DISK_BUS_NONE:
case VIR_DOMAIN_DISK_BUS_VIRTIO:
case VIR_DOMAIN_DISK_BUS_XEN:
@@ -15026,6 +15036,10 @@ virDomainDiskControllerMatch(int controller_type, int disk_bus)
disk_bus == VIR_DOMAIN_DISK_BUS_SATA)
return true;
+ if (controller_type == VIR_DOMAIN_CONTROLLER_TYPE_NVME &&
+ disk_bus == VIR_DOMAIN_DISK_BUS_NVME)
+ return true;
+
return false;
}
@@ -22675,6 +22689,36 @@ virDomainDefMaybeAddSmartcardController(virDomainDef *def)
}
}
+static int
+virDomainDefMaybeAssignNvmeControllerSerials(virDomainDef *def)
+{
+ size_t i = 0;
+
+ for (i = 0; i < def->ndisks; i++) {
+ virDomainDiskDef *disk = def->disks[i];
+ virDomainControllerDef *ctrl = NULL;
+
+ if (!disk->serial ||
+ disk->bus != VIR_DOMAIN_DISK_BUS_NVME ||
+ def->disks[i]->info.type != VIR_DOMAIN_DEVICE_ADDRESS_TYPE_DRIVE)
+ continue;
+
+ ctrl = virDomainDeviceFindNvmeController(def, &disk->info.addr.drive);
+ if (ctrl) {
+ if (!ctrl->opts.nvmeopts.serial) {
+ ctrl->opts.nvmeopts.serial = g_strdup(disk->serial);
+ } else if (STRNEQ_NULLABLE(disk->serial, ctrl->opts.nvmeopts.serial))
{
+ virReportError(VIR_ERR_XML_DETAIL, "%s",
+ _("Conflicting NVME disk serial number, all disks on
a controller must have the same serial number as the controller itself"));
+ return -1;
+ }
+ }
+ }
+
+ return 0;
+}
+
+
/*
* Based on the declared <address/> info for any devices,
* add necessary drive controllers which are not already present
@@ -22692,6 +22736,8 @@ virDomainDefAddImplicitControllers(virDomainDef *def)
VIR_DOMAIN_DISK_BUS_IDE);
virDomainDefAddDiskControllersForType(def, VIR_DOMAIN_CONTROLLER_TYPE_SATA,
VIR_DOMAIN_DISK_BUS_SATA);
+ virDomainDefAddDiskControllersForType(def, VIR_DOMAIN_CONTROLLER_TYPE_NVME,
+ VIR_DOMAIN_DISK_BUS_NVME);
virDomainDefMaybeAddVirtioSerialController(def);
virDomainDefMaybeAddSmartcardController(def);
@@ -22724,6 +22770,9 @@ virDomainDefAddImplicitDevices(virDomainDef *def,
virDomainXMLOption *xmlopt)
}
virDomainDefAddImplicitControllers(def);
+ if (virDomainDefMaybeAssignNvmeControllerSerials(def) < 0)
+ return -1;
+
if (virDomainDefAddImplicitVideo(def, xmlopt) < 0)
return -1;
@@ -29685,8 +29734,10 @@ virDiskNameToBusDeviceIndex(virDomainDiskDef *disk,
int *busIdx,
int *devIdx)
{
- int idx = virDiskNameToIndex(disk->dst);
- if (idx < 0)
+ int idx = -1;
+ int nvme_ctrl = 0;
+
+ if (virDiskNameParse(disk->dst, &nvme_ctrl, &idx, NULL) < 0 || idx <
0)
return -1;
switch (disk->bus) {
@@ -29698,6 +29749,10 @@ virDiskNameToBusDeviceIndex(virDomainDiskDef *disk,
*busIdx = idx / 7;
*devIdx = idx % 7;
break;
+ case VIR_DOMAIN_DISK_BUS_NVME:
+ *busIdx = nvme_ctrl;
+ *devIdx = idx;
+ break;
case VIR_DOMAIN_DISK_BUS_FDC:
case VIR_DOMAIN_DISK_BUS_USB:
case VIR_DOMAIN_DISK_BUS_VIRTIO:
diff --git a/src/conf/domain_conf.h b/src/conf/domain_conf.h
index c54e4759a783..3001e51c8ea6 100644
--- a/src/conf/domain_conf.h
+++ b/src/conf/domain_conf.h
@@ -401,6 +401,7 @@ typedef enum {
VIR_DOMAIN_DISK_BUS_UML,
VIR_DOMAIN_DISK_BUS_SATA,
VIR_DOMAIN_DISK_BUS_SD,
+ VIR_DOMAIN_DISK_BUS_NVME,
VIR_DOMAIN_DISK_BUS_LAST
} virDomainDiskBus;
diff --git a/src/conf/domain_postparse.c b/src/conf/domain_postparse.c
index bf33f29638ff..a07ec8d94e65 100644
--- a/src/conf/domain_postparse.c
+++ b/src/conf/domain_postparse.c
@@ -523,6 +523,8 @@ virDomainDiskDefPostParse(virDomainDiskDef *disk,
disk->bus = VIR_DOMAIN_DISK_BUS_XEN;
else if (STRPREFIX(disk->dst, "ubd"))
disk->bus = VIR_DOMAIN_DISK_BUS_UML;
+ else if (STRPREFIX(disk->dst, "nvme"))
+ disk->bus = VIR_DOMAIN_DISK_BUS_NVME;
}
}
diff --git a/src/conf/domain_validate.c b/src/conf/domain_validate.c
index d0d4bc0bf4b7..96f76f2f7bba 100644
--- a/src/conf/domain_validate.c
+++ b/src/conf/domain_validate.c
@@ -260,6 +260,7 @@ virDomainDiskAddressDiskBusCompatibility(virDomainDiskBus bus,
case VIR_DOMAIN_DISK_BUS_FDC:
case VIR_DOMAIN_DISK_BUS_SCSI:
case VIR_DOMAIN_DISK_BUS_SATA:
+ case VIR_DOMAIN_DISK_BUS_NVME:
return addressType == VIR_DOMAIN_DEVICE_ADDRESS_TYPE_DRIVE;
case VIR_DOMAIN_DISK_BUS_VIRTIO:
case VIR_DOMAIN_DISK_BUS_XEN:
@@ -948,7 +949,8 @@ virDomainDiskDefValidate(const virDomainDef *def,
!STRPREFIX(disk->dst, "sd") &&
!STRPREFIX(disk->dst, "vd") &&
!STRPREFIX(disk->dst, "xvd") &&
- !STRPREFIX(disk->dst, "ubd")) {
+ !STRPREFIX(disk->dst, "ubd") &&
+ !STRPREFIX(disk->dst, "nvme")) {
virReportError(VIR_ERR_INTERNAL_ERROR,
_("Invalid harddisk device name: %1$s"), disk->dst);
return -1;
diff --git a/src/conf/schemas/domaincommon.rng b/src/conf/schemas/domaincommon.rng
index 029d4ed4ec7a..a5a0f337684c 100644
--- a/src/conf/schemas/domaincommon.rng
+++ b/src/conf/schemas/domaincommon.rng
@@ -2517,9 +2517,14 @@
</define>
<define name="diskTargetDev">
- <data type="string">
- <param
name="pattern">(ioemu:)?(fd|hd|sd|vd|xvd|ubd)[a-zA-Z0-9_]+</param>
- </data>
+ <choice>
+ <data type="string">
+ <param
name="pattern">(ioemu:)?(fd|hd|sd|vd|xvd|ubd)[a-zA-Z0-9_]+</param>
+ </data>
+ <data type="string">
+ <param name="pattern">nvme[0-9]+n[0-9]+(p[0-9]+)?</param>
+ </data>
+ </choice>
</define>
<define name="diskTarget">
@@ -2539,6 +2544,7 @@
<value>uml</value> <!-- NOT USED ANYMORE -->
<value>sata</value>
<value>sd</value>
+ <value>nvme</value>
</choice>
</attribute>
</optional>
diff --git a/src/hyperv/hyperv_driver.c b/src/hyperv/hyperv_driver.c
index 0d1e388c08d7..126453eda4ab 100644
--- a/src/hyperv/hyperv_driver.c
+++ b/src/hyperv/hyperv_driver.c
@@ -948,6 +948,7 @@ hypervDomainAttachStorage(virDomainPtr domain, virDomainDef *def,
const char *ho
case VIR_DOMAIN_DISK_BUS_UML:
case VIR_DOMAIN_DISK_BUS_SATA:
case VIR_DOMAIN_DISK_BUS_SD:
+ case VIR_DOMAIN_DISK_BUS_NVME:
case VIR_DOMAIN_DISK_BUS_LAST:
default:
virReportError(VIR_ERR_INTERNAL_ERROR, "%s", _("Unsupported
controller type"));
@@ -3078,6 +3079,7 @@ hypervDomainAttachDeviceFlags(virDomainPtr domain, const char *xml,
unsigned int
case VIR_DOMAIN_DISK_BUS_UML:
case VIR_DOMAIN_DISK_BUS_SATA:
case VIR_DOMAIN_DISK_BUS_SD:
+ case VIR_DOMAIN_DISK_BUS_NVME:
case VIR_DOMAIN_DISK_BUS_LAST:
default:
virReportError(VIR_ERR_INTERNAL_ERROR, "%s", _("Invalid disk
bus in definition"));
diff --git a/src/qemu/qemu_alias.c b/src/qemu/qemu_alias.c
index 3e6bced4a885..9d39ebd63da1 100644
--- a/src/qemu/qemu_alias.c
+++ b/src/qemu/qemu_alias.c
@@ -258,6 +258,7 @@ qemuAssignDeviceDiskAlias(virDomainDef *def,
case VIR_DOMAIN_DISK_BUS_IDE:
case VIR_DOMAIN_DISK_BUS_SATA:
case VIR_DOMAIN_DISK_BUS_SCSI:
+ case VIR_DOMAIN_DISK_BUS_NVME:
diskPriv->qomName = g_strdup(disk->info.alias);
break;
diff --git a/src/qemu/qemu_command.c b/src/qemu/qemu_command.c
index 07963e33b351..528a8fc8ca86 100644
--- a/src/qemu/qemu_command.c
+++ b/src/qemu/qemu_command.c
@@ -532,6 +532,7 @@ qemuBuildDeviceAddresDriveProps(virJSONValue *props,
break;
+ case VIR_DOMAIN_DISK_BUS_NVME:
case VIR_DOMAIN_DISK_BUS_VIRTIO:
case VIR_DOMAIN_DISK_BUS_USB:
case VIR_DOMAIN_DISK_BUS_XEN:
@@ -1722,6 +1723,7 @@ qemuBuildDiskDeviceProps(const virDomainDef *def,
driver = "floppy";
break;
+ case VIR_DOMAIN_DISK_BUS_NVME:
case VIR_DOMAIN_DISK_BUS_XEN:
case VIR_DOMAIN_DISK_BUS_UML:
case VIR_DOMAIN_DISK_BUS_SD:
diff --git a/src/qemu/qemu_domain_address.c b/src/qemu/qemu_domain_address.c
index 7bc769fc1934..9b2faf1e8e37 100644
--- a/src/qemu/qemu_domain_address.c
+++ b/src/qemu/qemu_domain_address.c
@@ -739,6 +739,7 @@ qemuDomainDeviceCalculatePCIConnectFlags(virDomainDeviceDef *dev,
case VIR_DOMAIN_DISK_BUS_UML:
case VIR_DOMAIN_DISK_BUS_SATA:
case VIR_DOMAIN_DISK_BUS_SD:
+ case VIR_DOMAIN_DISK_BUS_NVME:
case VIR_DOMAIN_DISK_BUS_NONE:
case VIR_DOMAIN_DISK_BUS_LAST:
return 0;
diff --git a/src/qemu/qemu_hotplug.c b/src/qemu/qemu_hotplug.c
index 7a1170b2ddd1..9427eec64384 100644
--- a/src/qemu/qemu_hotplug.c
+++ b/src/qemu/qemu_hotplug.c
@@ -1058,6 +1058,7 @@ qemuDomainAttachDeviceDiskLiveInternal(virQEMUDriver *driver,
/* Note that SD card hotplug support should be added only once
* they support '-device' (don't require -drive only).
* See also: qemuDiskBusIsSD */
+ case VIR_DOMAIN_DISK_BUS_NVME:
case VIR_DOMAIN_DISK_BUS_NONE:
case VIR_DOMAIN_DISK_BUS_LAST:
virReportError(VIR_ERR_OPERATION_UNSUPPORTED,
@@ -5776,6 +5777,7 @@ qemuDomainDetachPrepDisk(virDomainObj *vm,
case VIR_DOMAIN_DISK_BUS_SCSI:
break;
+ case VIR_DOMAIN_DISK_BUS_NVME:
case VIR_DOMAIN_DISK_BUS_IDE:
case VIR_DOMAIN_DISK_BUS_FDC:
case VIR_DOMAIN_DISK_BUS_XEN:
diff --git a/src/qemu/qemu_validate.c b/src/qemu/qemu_validate.c
index 8acc44747456..a264185f5f43 100644
--- a/src/qemu/qemu_validate.c
+++ b/src/qemu/qemu_validate.c
@@ -2945,6 +2945,7 @@ qemuValidateDomainDeviceDefDiskIOThreads(const virDomainDef *def,
case VIR_DOMAIN_DISK_BUS_SATA:
case VIR_DOMAIN_DISK_BUS_SD:
case VIR_DOMAIN_DISK_BUS_NONE:
+ case VIR_DOMAIN_DISK_BUS_NVME:
case VIR_DOMAIN_DISK_BUS_LAST:
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
_("IOThreads not available for bus %1$s target %2$s"),
@@ -3086,6 +3087,7 @@ qemuValidateDomainDeviceDefDiskFrontend(const virDomainDiskDef
*disk,
case VIR_DOMAIN_DISK_BUS_UML:
case VIR_DOMAIN_DISK_BUS_SATA:
case VIR_DOMAIN_DISK_BUS_SD:
+ case VIR_DOMAIN_DISK_BUS_NVME:
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
_("disk device='lun' is not supported for
bus='%1$s'"),
virDomainDiskBusTypeToString(disk->bus));
@@ -3201,6 +3203,7 @@ qemuValidateDomainDeviceDefDiskFrontend(const virDomainDiskDef
*disk,
break;
+ case VIR_DOMAIN_DISK_BUS_NVME:
case VIR_DOMAIN_DISK_BUS_XEN:
case VIR_DOMAIN_DISK_BUS_SD:
case VIR_DOMAIN_DISK_BUS_NONE:
@@ -3397,6 +3400,7 @@ qemuValidateDomainDeviceDefDiskTransient(const virDomainDiskDef
*disk,
case VIR_DOMAIN_DISK_BUS_UML:
case VIR_DOMAIN_DISK_BUS_SATA:
case VIR_DOMAIN_DISK_BUS_SD:
+ case VIR_DOMAIN_DISK_BUS_NVME:
case VIR_DOMAIN_DISK_BUS_NONE:
case VIR_DOMAIN_DISK_BUS_LAST:
default:
@@ -3418,6 +3422,7 @@ qemuValidateDomainDeviceDefDisk(const virDomainDiskDef *disk,
{
const char *driverName = virDomainDiskGetDriver(disk);
virStorageSource *n;
+ int nvme_ctrl;
int idx;
int partition;
@@ -3445,7 +3450,7 @@ qemuValidateDomainDeviceDefDisk(const virDomainDiskDef *disk,
return -1;
}
- if (virDiskNameParse(disk->dst, NULL, &idx, &partition) < 0) {
+ if (virDiskNameParse(disk->dst, &nvme_ctrl, &idx, &partition) < 0)
{
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
_("invalid disk target '%1$s'"), disk->dst);
return -1;
diff --git a/src/test/test_driver.c b/src/test/test_driver.c
index 6f18b2b2c820..25335d90027e 100644
--- a/src/test/test_driver.c
+++ b/src/test/test_driver.c
@@ -10344,6 +10344,7 @@ testDomainAttachDeviceDiskLiveInternal(testDriver *driver
G_GNUC_UNUSED,
case VIR_DOMAIN_DISK_BUS_UML:
case VIR_DOMAIN_DISK_BUS_SATA:
case VIR_DOMAIN_DISK_BUS_SD:
+ case VIR_DOMAIN_DISK_BUS_NVME:
case VIR_DOMAIN_DISK_BUS_NONE:
case VIR_DOMAIN_DISK_BUS_LAST:
virReportError(VIR_ERR_OPERATION_UNSUPPORTED,
@@ -10792,6 +10793,7 @@ testDomainDetachPrepDisk(virDomainObj *vm,
case VIR_DOMAIN_DISK_BUS_UML:
case VIR_DOMAIN_DISK_BUS_SATA:
case VIR_DOMAIN_DISK_BUS_SD:
+ case VIR_DOMAIN_DISK_BUS_NVME:
virReportError(VIR_ERR_OPERATION_UNSUPPORTED, "%s",
_("This type of disk cannot be hot unplugged"));
return -1;
diff --git a/src/vbox/vbox_common.c b/src/vbox/vbox_common.c
index 95c70671a752..ed21798b2c7e 100644
--- a/src/vbox/vbox_common.c
+++ b/src/vbox/vbox_common.c
@@ -1239,6 +1239,7 @@ vboxAttachDrives(virDomainDef *def, struct _vboxDriver *data,
IMachine *machine)
case VIR_DOMAIN_DISK_BUS_USB:
case VIR_DOMAIN_DISK_BUS_UML:
case VIR_DOMAIN_DISK_BUS_SD:
+ case VIR_DOMAIN_DISK_BUS_NVME:
case VIR_DOMAIN_DISK_BUS_NONE:
case VIR_DOMAIN_DISK_BUS_LAST:
vboxReportError(VIR_ERR_CONFIG_UNSUPPORTED,
diff --git a/src/vmx/vmx.c b/src/vmx/vmx.c
index 4a9ad11b42c4..7fa00669a717 100644
--- a/src/vmx/vmx.c
+++ b/src/vmx/vmx.c
@@ -2240,6 +2240,7 @@ virVMXGenerateDiskTarget(virDomainDiskDef *def,
prefix = "fd";
break;
+ case VIR_DOMAIN_DISK_BUS_NVME:
case VIR_DOMAIN_DISK_BUS_VIRTIO:
case VIR_DOMAIN_DISK_BUS_XEN:
case VIR_DOMAIN_DISK_BUS_USB:
diff --git a/tests/genericxml2xmlindata/disk-nvme-invalid-serials.xml
b/tests/genericxml2xmlindata/disk-nvme-invalid-serials.xml
new file mode 100644
index 000000000000..dcfe6eaacfbf
--- /dev/null
+++ b/tests/genericxml2xmlindata/disk-nvme-invalid-serials.xml
@@ -0,0 +1,29 @@
+<domain type='qemu'>
+ <name>bar</name>
+ <uuid>00010203-0405-4607-8809-0a0b0c0d0e0f</uuid>
+ <memory unit='KiB'>219136</memory>
+ <currentMemory unit='KiB'>219136</currentMemory>
+ <vcpu placement='static'>1</vcpu>
+ <os>
+ <type arch='x86_64' machine='pc'>hvm</type>
+ <boot dev='hd'/>
+ </os>
+ <clock offset='utc'/>
+ <on_poweroff>destroy</on_poweroff>
+ <on_reboot>restart</on_reboot>
+ <on_crash>destroy</on_crash>
+ <devices>
+ <disk type='block' device='disk'>
+ <driver name='qemu' type='raw'/>
+ <source dev='/dev/HostVG/QEMUGuest1'/>
+ <target dev='nvme0n1' bus='nvme'/>
+ <serial>abcdefgh</serial>
+ </disk>
+ <disk type='block' device='disk'>
+ <driver name='qemu' type='raw'/>
+ <source dev='/dev/HostVG/QEMUGuest2'/>
+ <target dev='nvme0n2' bus='nvme'/>
+ <serial>IJKLMNOP</serial>
+ </disk>
+ </devices>
+</domain>
diff --git a/tests/genericxml2xmlindata/disk-nvme.xml
b/tests/genericxml2xmlindata/disk-nvme.xml
new file mode 100644
index 000000000000..bdfec4ef7b7c
--- /dev/null
+++ b/tests/genericxml2xmlindata/disk-nvme.xml
@@ -0,0 +1,32 @@
+<domain type='qemu'>
+ <name>bar</name>
+ <uuid>00010203-0405-4607-8809-0a0b0c0d0e0f</uuid>
+ <memory unit='KiB'>219136</memory>
+ <currentMemory unit='KiB'>219136</currentMemory>
+ <vcpu placement='static'>1</vcpu>
+ <os>
+ <type arch='x86_64' machine='pc'>hvm</type>
+ <boot dev='hd'/>
+ </os>
+ <clock offset='utc'/>
+ <on_poweroff>destroy</on_poweroff>
+ <on_reboot>restart</on_reboot>
+ <on_crash>destroy</on_crash>
+ <devices>
+ <disk type='block' device='disk'>
+ <driver name='qemu' type='raw'/>
+ <source dev='/dev/HostVG/QEMUGuest1'/>
+ <target dev='nvme0n1' bus='nvme'/>
+ </disk>
+ <disk type='block' device='disk'>
+ <driver name='qemu' type='raw'/>
+ <source dev='/dev/HostVG/QEMUGuest2'/>
+ <target dev='nvme3n2' bus='nvme'/>
+ <serial>abcdefgh</serial>
+ </disk>
+ <controller type='nvme' index='0'/>
+ <controller type='nvme' index='1'>
+ <serial>CDEFGAHC</serial>
+ </controller>
+ </devices>
+</domain>
diff --git a/tests/genericxml2xmloutdata/disk-nvme.xml
b/tests/genericxml2xmloutdata/disk-nvme.xml
new file mode 100644
index 000000000000..4251e1288eaf
--- /dev/null
+++ b/tests/genericxml2xmloutdata/disk-nvme.xml
@@ -0,0 +1,38 @@
+<domain type='qemu'>
+ <name>bar</name>
+ <uuid>00010203-0405-4607-8809-0a0b0c0d0e0f</uuid>
+ <memory unit='KiB'>219136</memory>
+ <currentMemory unit='KiB'>219136</currentMemory>
+ <vcpu placement='static'>1</vcpu>
+ <os>
+ <type arch='x86_64' machine='pc'>hvm</type>
+ <boot dev='hd'/>
+ </os>
+ <clock offset='utc'/>
+ <on_poweroff>destroy</on_poweroff>
+ <on_reboot>restart</on_reboot>
+ <on_crash>destroy</on_crash>
+ <devices>
+ <disk type='block' device='disk'>
+ <driver name='qemu' type='raw'/>
+ <source dev='/dev/HostVG/QEMUGuest1'/>
+ <target dev='nvme0n1' bus='nvme'/>
+ <address type='drive' controller='0' bus='0'
target='0' unit='0'/>
+ </disk>
+ <disk type='block' device='disk'>
+ <driver name='qemu' type='raw'/>
+ <source dev='/dev/HostVG/QEMUGuest2'/>
+ <target dev='nvme3n2' bus='nvme'/>
+ <serial>abcdefgh</serial>
+ <address type='drive' controller='3' bus='0'
target='0' unit='1'/>
+ </disk>
+ <controller type='nvme' index='0'/>
+ <controller type='nvme' index='1'>
+ <serial>CDEFGAHC</serial>
+ </controller>
+ <controller type='nvme' index='2'/>
+ <controller type='nvme' index='3'>
+ <serial>abcdefgh</serial>
+ </controller>
+ </devices>
+</domain>
diff --git a/tests/genericxml2xmltest.c b/tests/genericxml2xmltest.c
index f4e04d84f825..6757fc44ded1 100644
--- a/tests/genericxml2xmltest.c
+++ b/tests/genericxml2xmltest.c
@@ -232,6 +232,8 @@ mymain(void)
DO_TEST("fibrechannel-appid");
DO_TEST("controller-nvme");
+ DO_TEST_DIFFERENT("disk-nvme");
+ DO_TEST_FAIL_INACTIVE("disk-nvme-invalid-serials");
#define DO_TEST_BACKUP_FULL(name, intrnl) \
do { \
--
2.49.0