[libvirt PATCH v2 00/10] Add ability to create mediated devices in libvirt

Apologies for the delay in posting the second version of this series. This is the first portion of an effort to support persistent mediated devices with libvirt. This first series simply enables creating and destroying non-persistent mediated devices via the virNodeDeviceCreateXML() and virNodeDeviceDestroy() functions. The 'mdevctl' utility[1] provides the backend implementation. Changes in v2: - review findings fixed - Added Unit testing - passed JSON config via stdin instead of a temporary file (see portability note in patch 5) [1] https://github.com/mdevctl/mdevctl Jonathon Jongsma (10): nodedev: factor out nodeDeviceHasCapability() nodedev: add support for mdev attributes nodedev: refactor nodeDeviceFindNewDevice() nodedev: store mdev UUID in mdev caps nodedev: add mdev support to virNodeDeviceCreateXML() nodedev: Build a non-loadable driver lib nodedev: Add testing for 'mdevctl start' nodedev: add mdev support to virNodeDeviceDestroy() nodedev: Add testing for 'mdevctl stop' docs: note node device fields that are read-only build-aux/syntax-check.mk | 2 +- docs/formatnode.html.in | 16 +- docs/schemas/nodedev.rng | 6 + libvirt.spec.in | 2 + m4/virt-external-programs.m4 | 3 + src/conf/node_device_conf.c | 60 ++- src/conf/node_device_conf.h | 3 + src/conf/virnodedeviceobj.c | 34 ++ src/conf/virnodedeviceobj.h | 3 + src/libvirt_private.syms | 3 + src/node_device/Makefile.inc.am | 23 +- src/node_device/node_device_driver.c | 377 ++++++++++++++++-- src/node_device/node_device_driver.h | 9 + src/node_device/node_device_udev.c | 5 +- src/util/virmdev.c | 12 + src/util/virmdev.h | 11 + tests/Makefile.am | 14 + ...019_36ea_4111_8f0a_8c9a70e21366-start.argv | 1 + ...019_36ea_4111_8f0a_8c9a70e21366-start.json | 1 + ...d39_495e_4243_ad9f_beb3f14c23d9-start.argv | 1 + ...d39_495e_4243_ad9f_beb3f14c23d9-start.json | 1 + ...916_1ca8_49ac_b176_871d16c13076-start.argv | 1 + ...916_1ca8_49ac_b176_871d16c13076-start.json | 1 + tests/nodedevmdevctldata/mdevctl-stop.argv | 1 + tests/nodedevmdevctltest.c | 300 ++++++++++++++ ...v_d069d019_36ea_4111_8f0a_8c9a70e21366.xml | 8 + ...v_d2441d39_495e_4243_ad9f_beb3f14c23d9.xml | 10 + ...v_fedc4916_1ca8_49ac_b176_871d16c13076.xml | 9 + 28 files changed, 862 insertions(+), 55 deletions(-) create mode 100644 tests/nodedevmdevctldata/mdev_d069d019_36ea_4111_8f0a_8c9a70e21366-start.argv create mode 100644 tests/nodedevmdevctldata/mdev_d069d019_36ea_4111_8f0a_8c9a70e21366-start.json create mode 100644 tests/nodedevmdevctldata/mdev_d2441d39_495e_4243_ad9f_beb3f14c23d9-start.argv create mode 100644 tests/nodedevmdevctldata/mdev_d2441d39_495e_4243_ad9f_beb3f14c23d9-start.json create mode 100644 tests/nodedevmdevctldata/mdev_fedc4916_1ca8_49ac_b176_871d16c13076-start.argv create mode 100644 tests/nodedevmdevctldata/mdev_fedc4916_1ca8_49ac_b176_871d16c13076-start.json create mode 100644 tests/nodedevmdevctldata/mdevctl-stop.argv create mode 100644 tests/nodedevmdevctltest.c create mode 100644 tests/nodedevschemadata/mdev_d069d019_36ea_4111_8f0a_8c9a70e21366.xml create mode 100644 tests/nodedevschemadata/mdev_d2441d39_495e_4243_ad9f_beb3f14c23d9.xml create mode 100644 tests/nodedevschemadata/mdev_fedc4916_1ca8_49ac_b176_871d16c13076.xml -- 2.21.3

Currently nodeDeviceCreateXML() and nodeDeviceDestroy() only support NPIV HBAs, but we want to be able to create mdev devices as well. This is a first step to enabling that support. Signed-off-by: Jonathon Jongsma <jjongsma@redhat.com> --- src/node_device/node_device_driver.c | 91 ++++++++++++++++++---------- 1 file changed, 58 insertions(+), 33 deletions(-) diff --git a/src/node_device/node_device_driver.c b/src/node_device/node_device_driver.c index ee175e1095..ba7ea50e5b 100644 --- a/src/node_device/node_device_driver.c +++ b/src/node_device/node_device_driver.c @@ -488,6 +488,21 @@ nodeDeviceFindNewDevice(virConnectPtr conn, } +static bool +nodeDeviceHasCapability(virNodeDeviceDefPtr def, virNodeDevCapType type) +{ + virNodeDevCapsDefPtr cap = def->caps; + + while (cap != NULL) { + if (cap->data.type == type) + return true; + cap = cap->next; + } + + return false; +} + + virNodeDevicePtr nodeDeviceCreateXML(virConnectPtr conn, const char *xmlDesc, @@ -513,24 +528,29 @@ nodeDeviceCreateXML(virConnectPtr conn, if (virNodeDeviceCreateXMLEnsureACL(conn, def) < 0) return NULL; - if (virNodeDeviceGetWWNs(def, &wwnn, &wwpn) == -1) - return NULL; + if (nodeDeviceHasCapability(def, VIR_NODE_DEV_CAP_SCSI_HOST)) { + if (virNodeDeviceGetWWNs(def, &wwnn, &wwpn) == -1) + return NULL; - if ((parent_host = virNodeDeviceObjListGetParentHost(driver->devs, def)) < 0) - return NULL; + if ((parent_host = virNodeDeviceObjListGetParentHost(driver->devs, def)) < 0) + return NULL; - if (virVHBAManageVport(parent_host, wwpn, wwnn, VPORT_CREATE) < 0) - return NULL; + if (virVHBAManageVport(parent_host, wwpn, wwnn, VPORT_CREATE) < 0) + return NULL; - device = nodeDeviceFindNewDevice(conn, wwnn, wwpn); - /* We don't check the return value, because one way or another, - * we're returning what we get... */ + device = nodeDeviceFindNewDevice(conn, wwnn, wwpn); + /* We don't check the return value, because one way or another, + * we're returning what we get... */ - if (device == NULL) - virReportError(VIR_ERR_NO_NODE_DEVICE, - _("no node device for '%s' with matching " - "wwnn '%s' and wwpn '%s'"), - def->name, wwnn, wwpn); + if (device == NULL) + virReportError(VIR_ERR_NO_NODE_DEVICE, + _("no node device for '%s' with matching " + "wwnn '%s' and wwpn '%s'"), + def->name, wwnn, wwpn); + } else { + virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s", + _("Unsupported device type")); + } return device; } @@ -557,31 +577,36 @@ nodeDeviceDestroy(virNodeDevicePtr device) if (virNodeDeviceDestroyEnsureACL(device->conn, def) < 0) goto cleanup; - if (virNodeDeviceGetWWNs(def, &wwnn, &wwpn) < 0) - goto cleanup; + if (nodeDeviceHasCapability(def, VIR_NODE_DEV_CAP_SCSI_HOST)) { + if (virNodeDeviceGetWWNs(def, &wwnn, &wwpn) < 0) + goto cleanup; - /* Because we're about to release the lock and thus run into a race - * possibility (however improbable) with a udevAddOneDevice change - * event which would essentially free the existing @def (obj->def) and - * replace it with something new, we need to grab the parent field - * and then find the parent obj in order to manage the vport */ - parent = g_strdup(def->parent); + /* Because we're about to release the lock and thus run into a race + * possibility (however improbable) with a udevAddOneDevice change + * event which would essentially free the existing @def (obj->def) and + * replace it with something new, we need to grab the parent field + * and then find the parent obj in order to manage the vport */ + parent = g_strdup(def->parent); - virNodeDeviceObjEndAPI(&obj); + virNodeDeviceObjEndAPI(&obj); - if (!(obj = virNodeDeviceObjListFindByName(driver->devs, parent))) { - virReportError(VIR_ERR_INTERNAL_ERROR, - _("cannot find parent '%s' definition"), parent); - goto cleanup; - } + if (!(obj = virNodeDeviceObjListFindByName(driver->devs, parent))) { + virReportError(VIR_ERR_INTERNAL_ERROR, + _("cannot find parent '%s' definition"), parent); + goto cleanup; + } - if (virSCSIHostGetNumber(parent, &parent_host) < 0) - goto cleanup; + if (virSCSIHostGetNumber(parent, &parent_host) < 0) + goto cleanup; - if (virVHBAManageVport(parent_host, wwpn, wwnn, VPORT_DELETE) < 0) - goto cleanup; + if (virVHBAManageVport(parent_host, wwpn, wwnn, VPORT_DELETE) < 0) + goto cleanup; - ret = 0; + ret = 0; + } else { + virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s", + _("Unsupported device type")); + } cleanup: virNodeDeviceObjEndAPI(&obj); -- 2.21.3

On Tue, Jun 09, 2020 at 04:43:41PM -0500, Jonathon Jongsma wrote:
Currently nodeDeviceCreateXML() and nodeDeviceDestroy() only support NPIV HBAs, but we want to be able to create mdev devices as well. This is a first step to enabling that support.
Signed-off-by: Jonathon Jongsma <jjongsma@redhat.com> --- Reviewed-by: Erik Skultety <eskultet@redhat.com>

Mediated devices support arbitrary vendor-specific attributes that can be attached to a mediated device. These attributes are ordered, and are written to sysfs in order after a device is created. This patch adds support for these attributes to the mdev data types and XML schema. Signed-off-by: Jonathon Jongsma <jjongsma@redhat.com> --- docs/formatnode.html.in | 7 +++++ docs/schemas/nodedev.rng | 6 ++++ src/conf/node_device_conf.c | 59 +++++++++++++++++++++++++++++++++++-- src/conf/node_device_conf.h | 2 ++ src/libvirt_private.syms | 2 ++ src/util/virmdev.c | 12 ++++++++ src/util/virmdev.h | 11 +++++++ 7 files changed, 96 insertions(+), 3 deletions(-) diff --git a/docs/formatnode.html.in b/docs/formatnode.html.in index 76eae928de..a46b73254b 100644 --- a/docs/formatnode.html.in +++ b/docs/formatnode.html.in @@ -393,6 +393,13 @@ which holds the IOMMU group number the mediated device belongs to. </dd> + <dt><code>attr</code></dt> + <dd> + This optional element can occur multiple times. It represents a + vendor-specific attribute that is used to configure this + mediated device. It has two required attributes: + <code>name</code> and <code>value</code>. + </dd> </dl> </dd> <dt><code>ccw</code></dt> diff --git a/docs/schemas/nodedev.rng b/docs/schemas/nodedev.rng index fe6ffa0b53..a1ce09af54 100644 --- a/docs/schemas/nodedev.rng +++ b/docs/schemas/nodedev.rng @@ -634,6 +634,12 @@ <ref name='unsignedInt'/> </attribute> </element> + <zeroOrMore> + <element name="attr"> + <attribute name="name"/> + <attribute name="value"/> + </element> + </zeroOrMore> </define> <define name='capccwdev'> diff --git a/src/conf/node_device_conf.c b/src/conf/node_device_conf.c index bccdbd0af8..045d146433 100644 --- a/src/conf/node_device_conf.c +++ b/src/conf/node_device_conf.c @@ -500,6 +500,21 @@ virNodeDeviceCapStorageDefFormat(virBufferPtr buf, virBufferAddLit(buf, "<capability type='hotpluggable'/>\n"); } +static void +virNodeDeviceCapMdevDefFormat(virBufferPtr buf, + const virNodeDevCapData *data) +{ + size_t i; + + virBufferEscapeString(buf, "<type id='%s'/>\n", data->mdev.type); + virBufferAsprintf(buf, "<iommuGroup number='%u'/>\n", + data->mdev.iommuGroupNumber); + for (i = 0; i < data->mdev.nattributes; i++) { + virMediatedDeviceAttrPtr attr = data->mdev.attributes[i]; + virBufferAsprintf(buf, "<attr name='%s' value='%s'/>\n", + attr->name, attr->value); + } +} char * virNodeDeviceDefFormat(const virNodeDeviceDef *def) @@ -583,9 +598,7 @@ virNodeDeviceDefFormat(const virNodeDeviceDef *def) virBufferEscapeString(&buf, "<type>%s</type>\n", virNodeDevDRMTypeToString(data->drm.type)); break; case VIR_NODE_DEV_CAP_MDEV: - virBufferEscapeString(&buf, "<type id='%s'/>\n", data->mdev.type); - virBufferAsprintf(&buf, "<iommuGroup number='%u'/>\n", - data->mdev.iommuGroupNumber); + virNodeDeviceCapMdevDefFormat(&buf, data); break; case VIR_NODE_DEV_CAP_CCW_DEV: virBufferAsprintf(&buf, "<cssid>0x%x</cssid>\n", @@ -1757,6 +1770,35 @@ virNodeDevCapSystemParseXML(xmlXPathContextPtr ctxt, return ret; } +static int +virNodeDevCapMdevAttributeParseXML(xmlXPathContextPtr ctxt, + xmlNodePtr node, + virNodeDevCapMdevPtr mdev) +{ + xmlNodePtr orig_node = node; + ctxt->node = node; + int ret = -1; + g_autoptr(virMediatedDeviceAttr) attr = virMediatedDeviceAttrNew(); + + attr->name = virXPathString("string(./@name)", ctxt); + attr->value = virXPathString("string(./@value)", ctxt); + if (!attr->name || !attr->value) { + virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s", + _("mdev attribute missing name or value")); + goto cleanup; + } + + if (VIR_APPEND_ELEMENT(mdev->attributes, + mdev->nattributes, + attr) < 0) + goto cleanup; + + ret = 0; + + cleanup: + ctxt->node = orig_node; + return ret; +} static int virNodeDevCapMdevParseXML(xmlXPathContextPtr ctxt, @@ -1766,6 +1808,9 @@ virNodeDevCapMdevParseXML(xmlXPathContextPtr ctxt, { VIR_XPATH_NODE_AUTORESTORE(ctxt); int ret = -1; + int nattrs = 0; + xmlNodePtr *attrs; + size_t i; ctxt->node = node; @@ -1783,6 +1828,11 @@ virNodeDevCapMdevParseXML(xmlXPathContextPtr ctxt, "'%s'")) < 0) goto out; + if ((nattrs = virXPathNodeSet("./attr", ctxt, &attrs)) < 0) + goto out; + for (i = 0; i < nattrs; i++) + virNodeDevCapMdevAttributeParseXML(ctxt, attrs[i], mdev); + ret = 0; out: return ret; @@ -2172,6 +2222,9 @@ virNodeDevCapsDefFree(virNodeDevCapsDefPtr caps) break; case VIR_NODE_DEV_CAP_MDEV: VIR_FREE(data->mdev.type); + for (i = 0; i < data->mdev.nattributes; i++) + virMediatedDeviceAttrFree(data->mdev.attributes[i]); + VIR_FREE(data->mdev.attributes); break; case VIR_NODE_DEV_CAP_MDEV_TYPES: case VIR_NODE_DEV_CAP_DRM: diff --git a/src/conf/node_device_conf.h b/src/conf/node_device_conf.h index 9e4b0847fb..e3e1e788d4 100644 --- a/src/conf/node_device_conf.h +++ b/src/conf/node_device_conf.h @@ -141,6 +141,8 @@ typedef virNodeDevCapMdev *virNodeDevCapMdevPtr; struct _virNodeDevCapMdev { char *type; unsigned int iommuGroupNumber; + virMediatedDeviceAttrPtr *attributes; + size_t nattributes; }; typedef struct _virNodeDevCapPCIDev virNodeDevCapPCIDev; diff --git a/src/libvirt_private.syms b/src/libvirt_private.syms index a6af44fe1c..b8e6f058c3 100644 --- a/src/libvirt_private.syms +++ b/src/libvirt_private.syms @@ -2486,6 +2486,8 @@ virMacMapRemove; virMacMapWriteFile; # util/virmdev.h +virMediatedDeviceAttrFree; +virMediatedDeviceAttrNew; virMediatedDeviceFree; virMediatedDeviceGetIOMMUGroupDev; virMediatedDeviceGetIOMMUGroupNum; diff --git a/src/util/virmdev.c b/src/util/virmdev.c index 51a88a91d7..b8023dd991 100644 --- a/src/util/virmdev.c +++ b/src/util/virmdev.c @@ -512,3 +512,15 @@ virMediatedDeviceTypeReadAttrs(const char *sysfspath, return 0; } + +virMediatedDeviceAttrPtr virMediatedDeviceAttrNew(void) +{ + return g_new0(virMediatedDeviceAttr, 1); +} + +void virMediatedDeviceAttrFree(virMediatedDeviceAttrPtr attr) +{ + g_free(attr->name); + g_free(attr->value); + g_free(attr); +} diff --git a/src/util/virmdev.h b/src/util/virmdev.h index 51f7f608a2..eb167ccb48 100644 --- a/src/util/virmdev.h +++ b/src/util/virmdev.h @@ -37,6 +37,17 @@ typedef struct _virMediatedDevice virMediatedDevice; typedef virMediatedDevice *virMediatedDevicePtr; typedef struct _virMediatedDeviceList virMediatedDeviceList; typedef virMediatedDeviceList *virMediatedDeviceListPtr; +typedef struct _virMediatedDeviceAttr virMediatedDeviceAttr; +typedef virMediatedDeviceAttr *virMediatedDeviceAttrPtr; + +struct _virMediatedDeviceAttr { + char *name; + char *value; +}; + +virMediatedDeviceAttrPtr virMediatedDeviceAttrNew(void); +void virMediatedDeviceAttrFree(virMediatedDeviceAttrPtr attr); +G_DEFINE_AUTOPTR_CLEANUP_FUNC(virMediatedDeviceAttr, virMediatedDeviceAttrFree); G_DEFINE_AUTOPTR_CLEANUP_FUNC(virMediatedDeviceList, virObjectUnref); -- 2.21.3

On 6/9/20 11:43 PM, Jonathon Jongsma wrote:
Mediated devices support arbitrary vendor-specific attributes that can be attached to a mediated device. These attributes are ordered, and are written to sysfs in order after a device is created. This patch adds support for these attributes to the mdev data types and XML schema.
Signed-off-by: Jonathon Jongsma <jjongsma@redhat.com> --- docs/formatnode.html.in | 7 +++++ docs/schemas/nodedev.rng | 6 ++++ src/conf/node_device_conf.c | 59 +++++++++++++++++++++++++++++++++++-- src/conf/node_device_conf.h | 2 ++ src/libvirt_private.syms | 2 ++ src/util/virmdev.c | 12 ++++++++ src/util/virmdev.h | 11 +++++++ 7 files changed, 96 insertions(+), 3 deletions(-)
+static int +virNodeDevCapMdevAttributeParseXML(xmlXPathContextPtr ctxt, + xmlNodePtr node, + virNodeDevCapMdevPtr mdev) +{ + xmlNodePtr orig_node = node; + ctxt->node = node;
Alternatively, VIR_XPATH_NODE_AUTORESTORE() can be used.
+ int ret = -1; + g_autoptr(virMediatedDeviceAttr) attr = virMediatedDeviceAttrNew(); + + attr->name = virXPathString("string(./@name)", ctxt); + attr->value = virXPathString("string(./@value)", ctxt); + if (!attr->name || !attr->value) { + virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s", + _("mdev attribute missing name or value")); + goto cleanup; + } + + if (VIR_APPEND_ELEMENT(mdev->attributes, + mdev->nattributes, + attr) < 0) + goto cleanup; + + ret = 0; + + cleanup: + ctxt->node = orig_node; + return ret; +}
static int virNodeDevCapMdevParseXML(xmlXPathContextPtr ctxt, @@ -1766,6 +1808,9 @@ virNodeDevCapMdevParseXML(xmlXPathContextPtr ctxt, { VIR_XPATH_NODE_AUTORESTORE(ctxt); int ret = -1; + int nattrs = 0; + xmlNodePtr *attrs;
g_autofree becasue ..
+ size_t i;
ctxt->node = node;
@@ -1783,6 +1828,11 @@ virNodeDevCapMdevParseXML(xmlXPathContextPtr ctxt, "'%s'")) < 0) goto out;
+ if ((nattrs = virXPathNodeSet("./attr", ctxt, &attrs)) < 0) + goto out;
.. this allocates @attrs array.
+ for (i = 0; i < nattrs; i++) + virNodeDevCapMdevAttributeParseXML(ctxt, attrs[i], mdev); + ret = 0; out: return ret;
Just squash this in: diff --git i/src/conf/node_device_conf.c w/src/conf/node_device_conf.c index 045d146433..7b2a8c5545 100644 --- i/src/conf/node_device_conf.c +++ w/src/conf/node_device_conf.c @@ -1775,29 +1775,21 @@ virNodeDevCapMdevAttributeParseXML(xmlXPathContextPtr ctxt, xmlNodePtr node, virNodeDevCapMdevPtr mdev) { - xmlNodePtr orig_node = node; - ctxt->node = node; - int ret = -1; + VIR_XPATH_NODE_AUTORESTORE(ctxt); g_autoptr(virMediatedDeviceAttr) attr = virMediatedDeviceAttrNew(); + ctxt->node = node; attr->name = virXPathString("string(./@name)", ctxt); attr->value = virXPathString("string(./@value)", ctxt); if (!attr->name || !attr->value) { virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s", _("mdev attribute missing name or value")); - goto cleanup; + return -1; } - if (VIR_APPEND_ELEMENT(mdev->attributes, - mdev->nattributes, - attr) < 0) - goto cleanup; - - ret = 0; - - cleanup: - ctxt->node = orig_node; - return ret; + return VIR_APPEND_ELEMENT(mdev->attributes, + mdev->nattributes, + attr); } static int @@ -1809,7 +1801,7 @@ virNodeDevCapMdevParseXML(xmlXPathContextPtr ctxt, VIR_XPATH_NODE_AUTORESTORE(ctxt); int ret = -1; int nattrs = 0; - xmlNodePtr *attrs; + g_autofree xmlNodePtr *attrs = NULL; size_t i; ctxt->node = node; Michal

On Tue, Jun 09, 2020 at 04:43:42PM -0500, Jonathon Jongsma wrote:
Mediated devices support arbitrary vendor-specific attributes that can be attached to a mediated device. These attributes are ordered, and are written to sysfs in order after a device is created. This patch adds support for these attributes to the mdev data types and XML schema.
Signed-off-by: Jonathon Jongsma <jjongsma@redhat.com> --- docs/formatnode.html.in | 7 +++++ docs/schemas/nodedev.rng | 6 ++++ src/conf/node_device_conf.c | 59 +++++++++++++++++++++++++++++++++++-- src/conf/node_device_conf.h | 2 ++ src/libvirt_private.syms | 2 ++ src/util/virmdev.c | 12 ++++++++ src/util/virmdev.h | 11 +++++++ 7 files changed, 96 insertions(+), 3 deletions(-)
diff --git a/docs/formatnode.html.in b/docs/formatnode.html.in index 76eae928de..a46b73254b 100644 --- a/docs/formatnode.html.in +++ b/docs/formatnode.html.in @@ -393,6 +393,13 @@ which holds the IOMMU group number the mediated device belongs to. </dd> + <dt><code>attr</code></dt> + <dd> + This optional element can occur multiple times. It represents a + vendor-specific attribute that is used to configure this + mediated device. It has two required attributes: + <code>name</code> and <code>value</code>. + </dd> </dl> </dd> <dt><code>ccw</code></dt> diff --git a/docs/schemas/nodedev.rng b/docs/schemas/nodedev.rng index fe6ffa0b53..a1ce09af54 100644 --- a/docs/schemas/nodedev.rng +++ b/docs/schemas/nodedev.rng @@ -634,6 +634,12 @@ <ref name='unsignedInt'/> </attribute> </element> + <zeroOrMore> + <element name="attr"> + <attribute name="name"/> + <attribute name="value"/> + </element> + </zeroOrMore>
Only contextually related, but this patch should be preceded by one that makes iommugroup an optional element (this change would have to go to the parser as well). Since before this series, mdev XMLs were either not internally parsed at all or it would have come from inside libvirt, I'm saying this because even though we wouldn't break backwards compatibility, because we'd be relaxing the RNG and parser (which is ok), but I'd still like to see that change to take effect before this series is fully applied. With Michal's comments: Reviewed-by: Erik Skultety <eskultet@redhat.com>

In preparation for creating mediated devices in libvirt, we will need to wait for new mediated devices to be created as well. Refactor nodeDeviceFindNewDevice() so that we can re-use the main logic from this function to wait for different device types by passing a different 'find' function. Signed-off-by: Jonathon Jongsma <jjongsma@redhat.com> --- src/node_device/node_device_driver.c | 39 +++++++++++++++++++++++++--- 1 file changed, 35 insertions(+), 4 deletions(-) diff --git a/src/node_device/node_device_driver.c b/src/node_device/node_device_driver.c index ba7ea50e5b..629d4bcf91 100644 --- a/src/node_device/node_device_driver.c +++ b/src/node_device/node_device_driver.c @@ -447,6 +447,10 @@ nodeDeviceGetTime(time_t *t) } +typedef virNodeDevicePtr (*nodeDeviceFindNewDeviceFunc)(virConnectPtr conn, + const void* opaque); + + /* When large numbers of devices are present on the host, it's * possible for udev not to realize that it has work to do before we * get here. We thus keep trying to find the new device we just @@ -462,8 +466,8 @@ nodeDeviceGetTime(time_t *t) */ static virNodeDevicePtr nodeDeviceFindNewDevice(virConnectPtr conn, - const char *wwnn, - const char *wwpn) + nodeDeviceFindNewDeviceFunc func, + const void *opaque) { virNodeDevicePtr device = NULL; time_t start = 0, now = 0; @@ -474,7 +478,7 @@ nodeDeviceFindNewDevice(virConnectPtr conn, virWaitForDevices(); - device = nodeDeviceLookupSCSIHostByWWN(conn, wwnn, wwpn, 0); + device = func(conn, opaque); if (device != NULL) break; @@ -488,6 +492,33 @@ nodeDeviceFindNewDevice(virConnectPtr conn, } +typedef struct _NewSCSIHostFuncData NewSCSIHostFuncData; +struct _NewSCSIHostFuncData +{ + const char *wwnn; + const char *wwpn; +}; + + +static virNodeDevicePtr +nodeDeviceFindNewSCSIHostFunc(virConnectPtr conn, + const void *opaque) +{ + const NewSCSIHostFuncData *data = opaque; + return nodeDeviceLookupSCSIHostByWWN(conn, data->wwnn, data->wwpn, 0); +} + + +static virNodeDevicePtr +nodeDeviceFindNewSCSIHost(virConnectPtr conn, + const char *wwnn, + const char *wwpn) +{ + NewSCSIHostFuncData data = { .wwnn = wwnn, .wwpn = wwpn}; + return nodeDeviceFindNewDevice(conn, nodeDeviceFindNewSCSIHostFunc, &data); +} + + static bool nodeDeviceHasCapability(virNodeDeviceDefPtr def, virNodeDevCapType type) { @@ -538,7 +569,7 @@ nodeDeviceCreateXML(virConnectPtr conn, if (virVHBAManageVport(parent_host, wwpn, wwnn, VPORT_CREATE) < 0) return NULL; - device = nodeDeviceFindNewDevice(conn, wwnn, wwpn); + device = nodeDeviceFindNewSCSIHost(conn, wwnn, wwpn); /* We don't check the return value, because one way or another, * we're returning what we get... */ -- 2.21.3

On Tue, Jun 09, 2020 at 04:43:43PM -0500, Jonathon Jongsma wrote:
In preparation for creating mediated devices in libvirt, we will need to wait for new mediated devices to be created as well. Refactor nodeDeviceFindNewDevice() so that we can re-use the main logic from this function to wait for different device types by passing a different 'find' function.
Signed-off-by: Jonathon Jongsma <jjongsma@redhat.com> --- Reviewed-by: Erik Skultety <eskultet@redhat.com>

In order to allow libvirt to create and start new mediated devices, we need to be able to verify that the device has been started. In order to do this, we'll need to save the UUID of newly-discovered devices within the virNodeDevCapMdev structure. This allows us to search the device list by UUID and verify whether the expected device has been started. Signed-off-by: Jonathon Jongsma <jjongsma@redhat.com> --- src/conf/node_device_conf.c | 1 + src/conf/node_device_conf.h | 1 + src/node_device/node_device_udev.c | 5 ++--- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/conf/node_device_conf.c b/src/conf/node_device_conf.c index 045d146433..7bf00bb5bc 100644 --- a/src/conf/node_device_conf.c +++ b/src/conf/node_device_conf.c @@ -2222,6 +2222,7 @@ virNodeDevCapsDefFree(virNodeDevCapsDefPtr caps) break; case VIR_NODE_DEV_CAP_MDEV: VIR_FREE(data->mdev.type); + VIR_FREE(data->mdev.uuid); for (i = 0; i < data->mdev.nattributes; i++) virMediatedDeviceAttrFree(data->mdev.attributes[i]); VIR_FREE(data->mdev.attributes); diff --git a/src/conf/node_device_conf.h b/src/conf/node_device_conf.h index e3e1e788d4..9b8c7aadea 100644 --- a/src/conf/node_device_conf.h +++ b/src/conf/node_device_conf.h @@ -141,6 +141,7 @@ typedef virNodeDevCapMdev *virNodeDevCapMdevPtr; struct _virNodeDevCapMdev { char *type; unsigned int iommuGroupNumber; + char *uuid; virMediatedDeviceAttrPtr *attributes; size_t nattributes; }; diff --git a/src/node_device/node_device_udev.c b/src/node_device/node_device_udev.c index 386f23ef3a..bdf0b03add 100644 --- a/src/node_device/node_device_udev.c +++ b/src/node_device/node_device_udev.c @@ -1013,7 +1013,6 @@ udevProcessMediatedDevice(struct udev_device *dev, virNodeDeviceDefPtr def) { int ret = -1; - const char *uuidstr = NULL; int iommugrp = -1; char *linkpath = NULL; char *canonicalpath = NULL; @@ -1041,8 +1040,8 @@ udevProcessMediatedDevice(struct udev_device *dev, data->type = g_path_get_basename(canonicalpath); - uuidstr = udev_device_get_sysname(dev); - if ((iommugrp = virMediatedDeviceGetIOMMUGroupNum(uuidstr)) < 0) + data->uuid = g_strdup(udev_device_get_sysname(dev)); + if ((iommugrp = virMediatedDeviceGetIOMMUGroupNum(data->uuid)) < 0) goto cleanup; if (udevGenerateDeviceName(dev, def, NULL) != 0) -- 2.21.3

On Tue, Jun 09, 2020 at 04:43:44PM -0500, Jonathon Jongsma wrote:
In order to allow libvirt to create and start new mediated devices, we need to be able to verify that the device has been started. In order to do this, we'll need to save the UUID of newly-discovered devices within the virNodeDevCapMdev structure. This allows us to search the device list by UUID and verify whether the expected device has been started.
Signed-off-by: Jonathon Jongsma <jjongsma@redhat.com> --- Reviewed-by: Erik Skultety <eskultet@redhat.com>

With recent additions to the node device xml schema, an xml schema can now describe a mdev device sufficiently for libvirt to create and start the device using the mdevctl utility. Note that some of the the configuration for a mediated device must be passed to mdevctl as a JSON-formatted file. In order to avoid creating and cleaning up temporary files, the JSON is instead fed to stdin and we pass the filename /dev/stdin to mdevctl. While this may not be portable, neither are mediated devices, so I don't believe it should cause any problems. Signed-off-by: Jonathon Jongsma <jjongsma@redhat.com> --- libvirt.spec.in | 2 + m4/virt-external-programs.m4 | 3 + src/conf/virnodedeviceobj.c | 34 +++++ src/conf/virnodedeviceobj.h | 3 + src/libvirt_private.syms | 1 + src/node_device/node_device_driver.c | 203 +++++++++++++++++++++++++++ src/node_device/node_device_driver.h | 6 + 7 files changed, 252 insertions(+) diff --git a/libvirt.spec.in b/libvirt.spec.in index 262e66f3cc..2b3a4d2e71 100644 --- a/libvirt.spec.in +++ b/libvirt.spec.in @@ -522,6 +522,8 @@ Requires: libvirt-daemon = %{version}-%{release} Requires: libvirt-libs = %{version}-%{release} # needed for device enumeration Requires: systemd >= 185 +# For managing persistent mediated devices +Requires: mdevctl %description daemon-driver-nodedev The nodedev driver plugin for the libvirtd daemon, providing diff --git a/m4/virt-external-programs.m4 b/m4/virt-external-programs.m4 index 9046e3bf07..bd3cb1f757 100644 --- a/m4/virt-external-programs.m4 +++ b/m4/virt-external-programs.m4 @@ -65,6 +65,7 @@ AC_DEFUN([LIBVIRT_CHECK_EXTERNAL_PROGRAMS], [ AC_PATH_PROG([OVSVSCTL], [ovs-vsctl], [ovs-vsctl], [$LIBVIRT_SBIN_PATH]) AC_PATH_PROG([SCRUB], [scrub], [scrub], [$LIBVIRT_SBIN_PATH]) AC_PATH_PROG([ADDR2LINE], [addr2line], [addr2line], [$LIBVIRT_SBIN_PATH]) + AC_PATH_PROG([MDEVCTL], [mdevctl], [mdevctl], [$LIBVIRT_SBIN_PATH]) AC_DEFINE_UNQUOTED([DMIDECODE], ["$DMIDECODE"], [Location or name of the dmidecode program]) @@ -88,6 +89,8 @@ AC_DEFUN([LIBVIRT_CHECK_EXTERNAL_PROGRAMS], [ [Location or name of the scrub program (for wiping algorithms)]) AC_DEFINE_UNQUOTED([ADDR2LINE], ["$ADDR2LINE"], [Location of addr2line program]) + AC_DEFINE_UNQUOTED([MDEVCTL], ["$MDEVCTL"], + [Location or name of the mdevctl program]) AC_PATH_PROG([IP_PATH], [ip], [/sbin/ip], [$LIBVIRT_SBIN_PATH]) AC_DEFINE_UNQUOTED([IP_PATH], ["$IP_PATH"], [path to ip binary]) diff --git a/src/conf/virnodedeviceobj.c b/src/conf/virnodedeviceobj.c index 3a34a324ca..fd20d5f9e2 100644 --- a/src/conf/virnodedeviceobj.c +++ b/src/conf/virnodedeviceobj.c @@ -399,6 +399,40 @@ virNodeDeviceObjListFindSCSIHostByWWNs(virNodeDeviceObjListPtr devs, &data); } +static int +virNodeDeviceObjListFindMediatedDeviceByUUIDCallback(const void *payload, + const void *name G_GNUC_UNUSED, + const void *opaque G_GNUC_UNUSED) +{ + virNodeDeviceObjPtr obj = (virNodeDeviceObjPtr) payload; + const char *uuid = (const char *) opaque; + virNodeDevCapsDefPtr cap; + int want = 0; + + virObjectLock(obj); + + for (cap = obj->def->caps; cap != NULL; cap = cap->next) { + if (cap->data.type == VIR_NODE_DEV_CAP_MDEV) { + if (STREQ(cap->data.mdev.uuid, uuid)) { + want = 1; + break; + } + } + } + + virObjectUnlock(obj); + return want; +} + + +virNodeDeviceObjPtr +virNodeDeviceObjListFindMediatedDeviceByUUID(virNodeDeviceObjListPtr devs, + const char *uuid) +{ + return virNodeDeviceObjListSearch(devs, + virNodeDeviceObjListFindMediatedDeviceByUUIDCallback, + uuid); +} static void virNodeDeviceObjListDispose(void *obj) diff --git a/src/conf/virnodedeviceobj.h b/src/conf/virnodedeviceobj.h index c9df8dedab..6efdb23d36 100644 --- a/src/conf/virnodedeviceobj.h +++ b/src/conf/virnodedeviceobj.h @@ -118,3 +118,6 @@ virNodeDeviceObjListExport(virConnectPtr conn, void virNodeDeviceObjSetSkipUpdateCaps(virNodeDeviceObjPtr obj, bool skipUpdateCaps); +virNodeDeviceObjPtr +virNodeDeviceObjListFindMediatedDeviceByUUID(virNodeDeviceObjListPtr devs, + const char *uuid); diff --git a/src/libvirt_private.syms b/src/libvirt_private.syms index b8e6f058c3..532169d93d 100644 --- a/src/libvirt_private.syms +++ b/src/libvirt_private.syms @@ -1178,6 +1178,7 @@ virNodeDeviceObjListAssignDef; virNodeDeviceObjListExport; virNodeDeviceObjListFindByName; virNodeDeviceObjListFindBySysfsPath; +virNodeDeviceObjListFindMediatedDeviceByUUID; virNodeDeviceObjListFindSCSIHostByWWNs; virNodeDeviceObjListFree; virNodeDeviceObjListGetNames; diff --git a/src/node_device/node_device_driver.c b/src/node_device/node_device_driver.c index 629d4bcf91..dbc7eb4d1e 100644 --- a/src/node_device/node_device_driver.c +++ b/src/node_device/node_device_driver.c @@ -30,6 +30,7 @@ #include "datatypes.h" #include "viralloc.h" #include "virfile.h" +#include "virjson.h" #include "virstring.h" #include "node_device_conf.h" #include "node_device_event.h" @@ -40,6 +41,7 @@ #include "viraccessapicheck.h" #include "virnetdev.h" #include "virutil.h" +#include "vircommand.h" #define VIR_FROM_THIS VIR_FROM_NODEDEV @@ -304,6 +306,30 @@ nodeDeviceLookupSCSIHostByWWN(virConnectPtr conn, return device; } +static virNodeDevicePtr +nodeDeviceLookupMediatedDeviceByUUID(virConnectPtr conn, + const char *uuid, + unsigned int flags) +{ + virNodeDeviceObjPtr obj = NULL; + virNodeDeviceDefPtr def; + virNodeDevicePtr device = NULL; + + virCheckFlags(0, NULL); + + if (!(obj = virNodeDeviceObjListFindMediatedDeviceByUUID(driver->devs, + uuid))) + return NULL; + + def = virNodeDeviceObjGetDef(obj); + + if ((device = virGetNodeDevice(conn, def->name))) + device->parentName = g_strdup(def->parent); + + virNodeDeviceObjEndAPI(&obj); + return device; +} + char * nodeDeviceGetXMLDesc(virNodeDevicePtr device, @@ -492,6 +518,25 @@ nodeDeviceFindNewDevice(virConnectPtr conn, } +static virNodeDevicePtr +nodeDeviceFindNewMediatedDeviceFunc(virConnectPtr conn, + const void *opaque) +{ + const char *uuid = opaque; + return nodeDeviceLookupMediatedDeviceByUUID(conn, uuid, 0); +} + + +static virNodeDevicePtr +nodeDeviceFindNewMediatedDevice(virConnectPtr conn, + const char *mdev_uuid) +{ + return nodeDeviceFindNewDevice(conn, + nodeDeviceFindNewMediatedDeviceFunc, + mdev_uuid); +} + + typedef struct _NewSCSIHostFuncData NewSCSIHostFuncData; struct _NewSCSIHostFuncData { @@ -534,6 +579,162 @@ nodeDeviceHasCapability(virNodeDeviceDefPtr def, virNodeDevCapType type) } +/* format a json string that provides configuration information about this mdev + * to the mdevctl utility */ +static int +nodeDeviceDefToMdevctlConfig(virNodeDeviceDefPtr def, char **buf) +{ + size_t i; + virNodeDevCapMdevPtr mdev = &def->caps->data.mdev; + g_autoptr(virJSONValue) json = virJSONValueNewObject(); + + if (virJSONValueObjectAppendString(json, "mdev_type", mdev->type) < 0) + return -1; + if (virJSONValueObjectAppendString(json, "start", "manual") < 0) + return -1; + if (mdev->attributes) { + g_autoptr(virJSONValue) attributes = virJSONValueNewArray(); + for (i = 0; i < mdev->nattributes; i++) { + virMediatedDeviceAttrPtr attr = mdev->attributes[i]; + g_autoptr(virJSONValue) jsonattr = virJSONValueNewObject(); + + if (virJSONValueObjectAppendString(jsonattr, attr->name, attr->value) < 0) + return -1; + if (virJSONValueArrayAppend(attributes, g_steal_pointer(&jsonattr)) < 0) + return -1; + } + if (virJSONValueObjectAppend(json, "attrs", g_steal_pointer(&attributes)) < 0) + return -1; + } + + *buf = virJSONValueToString(json, false); + if (!*buf) + return -1; + + return 0; +} + + +static char * +nodeDeviceFindAddressByName(const char *name) +{ + virNodeDeviceDefPtr def = NULL; + virNodeDevCapsDefPtr caps = NULL; + char *pci_addr = NULL; + virNodeDeviceObjPtr dev = virNodeDeviceObjListFindByName(driver->devs, name); + + if (!dev) { + virReportError(VIR_ERR_NO_NODE_DEVICE, + _("could not find device '%s'"), name); + return NULL; + } + + def = virNodeDeviceObjGetDef(dev); + for (caps = def->caps; caps != NULL; caps = caps->next) { + if (caps->data.type == VIR_NODE_DEV_CAP_PCI_DEV) { + virPCIDeviceAddress addr = { + .domain = caps->data.pci_dev.domain, + .bus = caps->data.pci_dev.bus, + .slot = caps->data.pci_dev.slot, + .function = caps->data.pci_dev.function + }; + + pci_addr = virPCIDeviceAddressAsString(&addr); + break; + } + } + + virNodeDeviceObjEndAPI(&dev); + + return pci_addr; +} + + +virCommandPtr +nodeDeviceGetMdevctlStartCommand(virNodeDeviceDefPtr def, + bool persist, + char **uuid_out) +{ + virCommandPtr cmd; + const char *subcommand; + g_autofree char *json = NULL; + g_autofree char *mdevctl = virFindFileInPath(MDEVCTL); + g_autofree char *parent_pci = nodeDeviceFindAddressByName(def->parent); + + if (!mdevctl) + return NULL; + + if (!parent_pci) { + virReportError(VIR_ERR_NO_NODE_DEVICE, + _("unable to find PCI address for parent device '%s'"), def->parent); + return NULL; + } + + if (nodeDeviceDefToMdevctlConfig(def, &json) < 0) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("couldn't convert node device def to mdevctl JSON")); + return NULL; + } + + if (persist) + subcommand = "define"; + else + subcommand = "start"; + + cmd = virCommandNewArgList(mdevctl, subcommand, + "-p", parent_pci, + "--jsonfile", "/dev/stdin", + NULL); + + virCommandSetInputBuffer(cmd, json); + virCommandSetOutputBuffer(cmd, uuid_out); + + return cmd; +} + +static int +virMdevctlStart(virNodeDeviceDefPtr def, char **uuid) +{ + int status; + g_autoptr(virCommand) cmd = nodeDeviceGetMdevctlStartCommand(def, false, + uuid); + if (!cmd) + return -1; + + /* an auto-generated uuid is returned via stdout if no uuid is specified in + * the mdevctl args */ + if (virCommandRun(cmd, &status) < 0 || status != 0) + return -1; + + /* remove newline */ + *uuid = g_strstrip(*uuid); + + return 0; +} + + +static virNodeDevicePtr +nodeDeviceCreateXMLMdev(virConnectPtr conn, + virNodeDeviceDefPtr def) +{ + g_autofree char *uuid = NULL; + + if (!def->parent) { + virReportError(VIR_ERR_XML_ERROR, "%s", + _("cannot create a mediated device without a parent")); + return NULL; + } + + if (virMdevctlStart(def, &uuid) < 0) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("Unable to start mediated device")); + return NULL; + } + + return nodeDeviceFindNewMediatedDevice(conn, uuid); +} + + virNodeDevicePtr nodeDeviceCreateXML(virConnectPtr conn, const char *xmlDesc, @@ -578,6 +779,8 @@ nodeDeviceCreateXML(virConnectPtr conn, _("no node device for '%s' with matching " "wwnn '%s' and wwpn '%s'"), def->name, wwnn, wwpn); + } else if (nodeDeviceHasCapability(def, VIR_NODE_DEV_CAP_MDEV)) { + device = nodeDeviceCreateXMLMdev(conn, def); } else { virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s", _("Unsupported device type")); diff --git a/src/node_device/node_device_driver.h b/src/node_device/node_device_driver.h index eae5e2cb17..576f75375f 100644 --- a/src/node_device/node_device_driver.h +++ b/src/node_device/node_device_driver.h @@ -24,6 +24,7 @@ #include "internal.h" #include "driver.h" #include "virnodedeviceobj.h" +#include "vircommand.h" #define LINUX_NEW_DEVICE_WAIT_TIME 60 @@ -116,3 +117,8 @@ nodeConnectNodeDeviceEventRegisterAny(virConnectPtr conn, int nodeConnectNodeDeviceEventDeregisterAny(virConnectPtr conn, int callbackID); + +virCommandPtr +nodeDeviceGetMdevctlStartCommand(virNodeDeviceDefPtr def, + bool persist, + char **uuid_out); -- 2.21.3

On 6/9/20 11:43 PM, Jonathon Jongsma wrote:
With recent additions to the node device xml schema, an xml schema can now describe a mdev device sufficiently for libvirt to create and start the device using the mdevctl utility.
Note that some of the the configuration for a mediated device must be passed to mdevctl as a JSON-formatted file. In order to avoid creating and cleaning up temporary files, the JSON is instead fed to stdin and we pass the filename /dev/stdin to mdevctl. While this may not be portable, neither are mediated devices, so I don't believe it should cause any problems.
Agreed. mdevs are Linux specific and /dev/stdin is UNIX-wide understood (which is a strict superset of the former), so we are safe on that front.
Signed-off-by: Jonathon Jongsma <jjongsma@redhat.com> --- libvirt.spec.in | 2 + m4/virt-external-programs.m4 | 3 + src/conf/virnodedeviceobj.c | 34 +++++ src/conf/virnodedeviceobj.h | 3 + src/libvirt_private.syms | 1 + src/node_device/node_device_driver.c | 203 +++++++++++++++++++++++++++ src/node_device/node_device_driver.h | 6 + 7 files changed, 252 insertions(+)
diff --git a/libvirt.spec.in b/libvirt.spec.in index 262e66f3cc..2b3a4d2e71 100644 --- a/libvirt.spec.in +++ b/libvirt.spec.in @@ -522,6 +522,8 @@ Requires: libvirt-daemon = %{version}-%{release} Requires: libvirt-libs = %{version}-%{release} # needed for device enumeration Requires: systemd >= 185 +# For managing persistent mediated devices +Requires: mdevctl
%description daemon-driver-nodedev The nodedev driver plugin for the libvirtd daemon, providing diff --git a/m4/virt-external-programs.m4 b/m4/virt-external-programs.m4 index 9046e3bf07..bd3cb1f757 100644 --- a/m4/virt-external-programs.m4 +++ b/m4/virt-external-programs.m4 @@ -65,6 +65,7 @@ AC_DEFUN([LIBVIRT_CHECK_EXTERNAL_PROGRAMS], [ AC_PATH_PROG([OVSVSCTL], [ovs-vsctl], [ovs-vsctl], [$LIBVIRT_SBIN_PATH]) AC_PATH_PROG([SCRUB], [scrub], [scrub], [$LIBVIRT_SBIN_PATH]) AC_PATH_PROG([ADDR2LINE], [addr2line], [addr2line], [$LIBVIRT_SBIN_PATH]) + AC_PATH_PROG([MDEVCTL], [mdevctl], [mdevctl], [$LIBVIRT_SBIN_PATH])
This will need to look slightly different - see 07/10.
AC_DEFINE_UNQUOTED([DMIDECODE], ["$DMIDECODE"], [Location or name of the dmidecode program]) @@ -88,6 +89,8 @@ AC_DEFUN([LIBVIRT_CHECK_EXTERNAL_PROGRAMS], [ [Location or name of the scrub program (for wiping algorithms)]) AC_DEFINE_UNQUOTED([ADDR2LINE], ["$ADDR2LINE"], [Location of addr2line program]) + AC_DEFINE_UNQUOTED([MDEVCTL], ["$MDEVCTL"], + [Location or name of the mdevctl program])
AC_PATH_PROG([IP_PATH], [ip], [/sbin/ip], [$LIBVIRT_SBIN_PATH]) AC_DEFINE_UNQUOTED([IP_PATH], ["$IP_PATH"], [path to ip binary]) diff --git a/src/conf/virnodedeviceobj.c b/src/conf/virnodedeviceobj.c index 3a34a324ca..fd20d5f9e2 100644 --- a/src/conf/virnodedeviceobj.c +++ b/src/conf/virnodedeviceobj.c @@ -399,6 +399,40 @@ virNodeDeviceObjListFindSCSIHostByWWNs(virNodeDeviceObjListPtr devs, &data); }
+static int +virNodeDeviceObjListFindMediatedDeviceByUUIDCallback(const void *payload, + const void *name G_GNUC_UNUSED, + const void *opaque G_GNUC_UNUSED) +{ + virNodeDeviceObjPtr obj = (virNodeDeviceObjPtr) payload; + const char *uuid = (const char *) opaque; + virNodeDevCapsDefPtr cap; + int want = 0; + + virObjectLock(obj); + + for (cap = obj->def->caps; cap != NULL; cap = cap->next) { + if (cap->data.type == VIR_NODE_DEV_CAP_MDEV) { + if (STREQ(cap->data.mdev.uuid, uuid)) { + want = 1; + break; + } + } + } + + virObjectUnlock(obj); + return want; +} + + +virNodeDeviceObjPtr +virNodeDeviceObjListFindMediatedDeviceByUUID(virNodeDeviceObjListPtr devs, + const char *uuid) +{ + return virNodeDeviceObjListSearch(devs, + virNodeDeviceObjListFindMediatedDeviceByUUIDCallback, + uuid); +}
static void virNodeDeviceObjListDispose(void *obj) diff --git a/src/conf/virnodedeviceobj.h b/src/conf/virnodedeviceobj.h index c9df8dedab..6efdb23d36 100644 --- a/src/conf/virnodedeviceobj.h +++ b/src/conf/virnodedeviceobj.h @@ -118,3 +118,6 @@ virNodeDeviceObjListExport(virConnectPtr conn, void virNodeDeviceObjSetSkipUpdateCaps(virNodeDeviceObjPtr obj, bool skipUpdateCaps); +virNodeDeviceObjPtr +virNodeDeviceObjListFindMediatedDeviceByUUID(virNodeDeviceObjListPtr devs, + const char *uuid); diff --git a/src/libvirt_private.syms b/src/libvirt_private.syms index b8e6f058c3..532169d93d 100644 --- a/src/libvirt_private.syms +++ b/src/libvirt_private.syms @@ -1178,6 +1178,7 @@ virNodeDeviceObjListAssignDef; virNodeDeviceObjListExport; virNodeDeviceObjListFindByName; virNodeDeviceObjListFindBySysfsPath; +virNodeDeviceObjListFindMediatedDeviceByUUID; virNodeDeviceObjListFindSCSIHostByWWNs; virNodeDeviceObjListFree; virNodeDeviceObjListGetNames; diff --git a/src/node_device/node_device_driver.c b/src/node_device/node_device_driver.c index 629d4bcf91..dbc7eb4d1e 100644 --- a/src/node_device/node_device_driver.c +++ b/src/node_device/node_device_driver.c @@ -30,6 +30,7 @@ #include "datatypes.h" #include "viralloc.h" #include "virfile.h" +#include "virjson.h" #include "virstring.h" #include "node_device_conf.h" #include "node_device_event.h" @@ -40,6 +41,7 @@ #include "viraccessapicheck.h" #include "virnetdev.h" #include "virutil.h" +#include "vircommand.h"
#define VIR_FROM_THIS VIR_FROM_NODEDEV
@@ -304,6 +306,30 @@ nodeDeviceLookupSCSIHostByWWN(virConnectPtr conn, return device; }
+static virNodeDevicePtr +nodeDeviceLookupMediatedDeviceByUUID(virConnectPtr conn, + const char *uuid, + unsigned int flags) +{ + virNodeDeviceObjPtr obj = NULL; + virNodeDeviceDefPtr def; + virNodeDevicePtr device = NULL; + + virCheckFlags(0, NULL); + + if (!(obj = virNodeDeviceObjListFindMediatedDeviceByUUID(driver->devs, + uuid))) + return NULL; + + def = virNodeDeviceObjGetDef(obj); + + if ((device = virGetNodeDevice(conn, def->name))) + device->parentName = g_strdup(def->parent); + + virNodeDeviceObjEndAPI(&obj); + return device; +} +
char * nodeDeviceGetXMLDesc(virNodeDevicePtr device, @@ -492,6 +518,25 @@ nodeDeviceFindNewDevice(virConnectPtr conn, }
+static virNodeDevicePtr +nodeDeviceFindNewMediatedDeviceFunc(virConnectPtr conn, + const void *opaque) +{ + const char *uuid = opaque; + return nodeDeviceLookupMediatedDeviceByUUID(conn, uuid, 0); +} + + +static virNodeDevicePtr +nodeDeviceFindNewMediatedDevice(virConnectPtr conn, + const char *mdev_uuid) +{ + return nodeDeviceFindNewDevice(conn, + nodeDeviceFindNewMediatedDeviceFunc, + mdev_uuid); +} + + typedef struct _NewSCSIHostFuncData NewSCSIHostFuncData; struct _NewSCSIHostFuncData { @@ -534,6 +579,162 @@ nodeDeviceHasCapability(virNodeDeviceDefPtr def, virNodeDevCapType type) }
+/* format a json string that provides configuration information about this mdev + * to the mdevctl utility */ +static int +nodeDeviceDefToMdevctlConfig(virNodeDeviceDefPtr def, char **buf) +{ + size_t i; + virNodeDevCapMdevPtr mdev = &def->caps->data.mdev; + g_autoptr(virJSONValue) json = virJSONValueNewObject(); + + if (virJSONValueObjectAppendString(json, "mdev_type", mdev->type) < 0) + return -1; + if (virJSONValueObjectAppendString(json, "start", "manual") < 0) + return -1; + if (mdev->attributes) { + g_autoptr(virJSONValue) attributes = virJSONValueNewArray(); + for (i = 0; i < mdev->nattributes; i++) { + virMediatedDeviceAttrPtr attr = mdev->attributes[i]; + g_autoptr(virJSONValue) jsonattr = virJSONValueNewObject(); + + if (virJSONValueObjectAppendString(jsonattr, attr->name, attr->value) < 0) + return -1; + if (virJSONValueArrayAppend(attributes, g_steal_pointer(&jsonattr)) < 0) + return -1; + } + if (virJSONValueObjectAppend(json, "attrs", g_steal_pointer(&attributes)) < 0) + return -1; + } + + *buf = virJSONValueToString(json, false); + if (!*buf) + return -1; + + return 0; +} + + +static char * +nodeDeviceFindAddressByName(const char *name) +{ + virNodeDeviceDefPtr def = NULL; + virNodeDevCapsDefPtr caps = NULL; + char *pci_addr = NULL; + virNodeDeviceObjPtr dev = virNodeDeviceObjListFindByName(driver->devs, name); + + if (!dev) { + virReportError(VIR_ERR_NO_NODE_DEVICE, + _("could not find device '%s'"), name); + return NULL; + } + + def = virNodeDeviceObjGetDef(dev); + for (caps = def->caps; caps != NULL; caps = caps->next) { + if (caps->data.type == VIR_NODE_DEV_CAP_PCI_DEV) { + virPCIDeviceAddress addr = { + .domain = caps->data.pci_dev.domain, + .bus = caps->data.pci_dev.bus, + .slot = caps->data.pci_dev.slot, + .function = caps->data.pci_dev.function + }; + + pci_addr = virPCIDeviceAddressAsString(&addr); + break; + } + } + + virNodeDeviceObjEndAPI(&dev); + + return pci_addr; +} + + +virCommandPtr +nodeDeviceGetMdevctlStartCommand(virNodeDeviceDefPtr def, + bool persist, + char **uuid_out) +{ + virCommandPtr cmd; + const char *subcommand; + g_autofree char *json = NULL; + g_autofree char *mdevctl = virFindFileInPath(MDEVCTL); + g_autofree char *parent_pci = nodeDeviceFindAddressByName(def->parent); + + if (!mdevctl) + return NULL;
This virFindFileInPath() and @mdevctl variable aren't necessary. virCommandRun() will call virFindFileInPath() under the hood (see v6.4.0-66-gf603b99ad9). And also, if the function fails, it does so silently. And this doesn't report error neither.
+ + if (!parent_pci) { + virReportError(VIR_ERR_NO_NODE_DEVICE, + _("unable to find PCI address for parent device '%s'"), def->parent); + return NULL; + } + + if (nodeDeviceDefToMdevctlConfig(def, &json) < 0) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("couldn't convert node device def to mdevctl JSON")); + return NULL; + } + + if (persist) + subcommand = "define"; + else + subcommand = "start"; + + cmd = virCommandNewArgList(mdevctl, subcommand, + "-p", parent_pci, + "--jsonfile", "/dev/stdin", + NULL); + + virCommandSetInputBuffer(cmd, json); + virCommandSetOutputBuffer(cmd, uuid_out); + + return cmd; +} + +static int +virMdevctlStart(virNodeDeviceDefPtr def, char **uuid) +{ + int status; + g_autoptr(virCommand) cmd = nodeDeviceGetMdevctlStartCommand(def, false, + uuid); + if (!cmd) + return -1; + + /* an auto-generated uuid is returned via stdout if no uuid is specified in + * the mdevctl args */ + if (virCommandRun(cmd, &status) < 0 || status != 0) + return -1; + + /* remove newline */ + *uuid = g_strstrip(*uuid); + + return 0; +} + + +static virNodeDevicePtr +nodeDeviceCreateXMLMdev(virConnectPtr conn, + virNodeDeviceDefPtr def) +{ + g_autofree char *uuid = NULL; + + if (!def->parent) { + virReportError(VIR_ERR_XML_ERROR, "%s", + _("cannot create a mediated device without a parent")); + return NULL; + } + + if (virMdevctlStart(def, &uuid) < 0) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("Unable to start mediated device")); + return NULL; + } + + return nodeDeviceFindNewMediatedDevice(conn, uuid); +} + + virNodeDevicePtr nodeDeviceCreateXML(virConnectPtr conn, const char *xmlDesc, @@ -578,6 +779,8 @@ nodeDeviceCreateXML(virConnectPtr conn, _("no node device for '%s' with matching " "wwnn '%s' and wwpn '%s'"), def->name, wwnn, wwpn); + } else if (nodeDeviceHasCapability(def, VIR_NODE_DEV_CAP_MDEV)) { + device = nodeDeviceCreateXMLMdev(conn, def); } else { virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s", _("Unsupported device type")); diff --git a/src/node_device/node_device_driver.h b/src/node_device/node_device_driver.h index eae5e2cb17..576f75375f 100644 --- a/src/node_device/node_device_driver.h +++ b/src/node_device/node_device_driver.h @@ -24,6 +24,7 @@ #include "internal.h" #include "driver.h" #include "virnodedeviceobj.h" +#include "vircommand.h"
#define LINUX_NEW_DEVICE_WAIT_TIME 60
@@ -116,3 +117,8 @@ nodeConnectNodeDeviceEventRegisterAny(virConnectPtr conn, int nodeConnectNodeDeviceEventDeregisterAny(virConnectPtr conn, int callbackID); + +virCommandPtr +nodeDeviceGetMdevctlStartCommand(virNodeDeviceDefPtr def, + bool persist, + char **uuid_out);

On Tue, Jun 09, 2020 at 04:43:45PM -0500, Jonathon Jongsma wrote:
With recent additions to the node device xml schema, an xml schema can now describe a mdev device sufficiently for libvirt to create and start the device using the mdevctl utility.
Note that some of the the configuration for a mediated device must be passed to mdevctl as a JSON-formatted file. In order to avoid creating and cleaning up temporary files, the JSON is instead fed to stdin and we pass the filename /dev/stdin to mdevctl. While this may not be portable, neither are mediated devices, so I don't believe it should cause any problems.
Signed-off-by: Jonathon Jongsma <jjongsma@redhat.com> --- ...
+static int +virNodeDeviceObjListFindMediatedDeviceByUUIDCallback(const void *payload, + const void *name G_GNUC_UNUSED, + const void *opaque G_GNUC_UNUSED)
opaque is not unused. ...
+ + +virCommandPtr +nodeDeviceGetMdevctlStartCommand(virNodeDeviceDefPtr def, + bool persist, + char **uuid_out) +{ + virCommandPtr cmd; + const char *subcommand; + g_autofree char *json = NULL; + g_autofree char *mdevctl = virFindFileInPath(MDEVCTL); + g_autofree char *parent_pci = nodeDeviceFindAddressByName(def->parent); + + if (!mdevctl) + return NULL; + + if (!parent_pci) { + virReportError(VIR_ERR_NO_NODE_DEVICE, + _("unable to find PCI address for parent device '%s'"), def->parent); + return NULL; + } + + if (nodeDeviceDefToMdevctlConfig(def, &json) < 0) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("couldn't convert node device def to mdevctl JSON")); + return NULL; + } + + if (persist) + subcommand = "define";
"define" doesn't create an mdev unless '--auto' is passed, so having this as part of MdevctlStartCommand feels confusing, I think this helper should be named GetMdevctlCmdline and let the caller pass the correct subcommand. Regards, Erik

In order to test the nodedev driver, we need to link against a non-loadable module. Similar to other loadable modules already in the repository, create an _impl library that can be linked against the unit tests and then create a loadable module from that. Signed-off-by: Jonathon Jongsma <jjongsma@redhat.com> --- src/node_device/Makefile.inc.am | 23 +++++++++++++---------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/src/node_device/Makefile.inc.am b/src/node_device/Makefile.inc.am index 788563665f..5993165b56 100644 --- a/src/node_device/Makefile.inc.am +++ b/src/node_device/Makefile.inc.am @@ -34,34 +34,37 @@ EXTRA_DIST += \ if WITH_NODE_DEVICES # Needed to keep automake quiet about conditionals +noinst_LTLIBRARIES += libvirt_driver_nodedev_impl.la +libvirt_driver_nodedev_la_SOURCES = +libvirt_driver_nodedev_la_LIBADD = libvirt_driver_nodedev_impl.la mod_LTLIBRARIES += libvirt_driver_nodedev.la -libvirt_driver_nodedev_la_SOURCES = $(NODE_DEVICE_DRIVER_SOURCES) +libvirt_driver_nodedev_impl_la_SOURCES = $(NODE_DEVICE_DRIVER_SOURCES) -libvirt_driver_nodedev_la_CFLAGS = \ +libvirt_driver_nodedev_impl_la_CFLAGS = \ -I$(srcdir)/access \ -I$(builddir)/access \ -I$(srcdir)/conf \ $(AM_CFLAGS) \ $(LIBNL_CFLAGS) \ $(NULL) -libvirt_driver_nodedev_la_LDFLAGS = $(AM_LDFLAGS_MOD_NOUNDEF) -libvirt_driver_nodedev_la_LIBADD = \ +libvirt_driver_nodedev_impl_la_LDFLAGS = $(AM_LDFLAGS_MOD_NOUNDEF) +libvirt_driver_nodedev_impl_la_LIBADD = \ libvirt.la \ $(GLIB_LIBS) \ $(NULL) if WITH_HAL -libvirt_driver_nodedev_la_SOURCES += $(NODE_DEVICE_DRIVER_HAL_SOURCES) -libvirt_driver_nodedev_la_CFLAGS += $(HAL_CFLAGS) -libvirt_driver_nodedev_la_LIBADD += $(HAL_LIBS) +libvirt_driver_nodedev_impl_la_SOURCES += $(NODE_DEVICE_DRIVER_HAL_SOURCES) +libvirt_driver_nodedev_impl_la_CFLAGS += $(HAL_CFLAGS) +libvirt_driver_nodedev_impl_la_LIBADD += $(HAL_LIBS) endif WITH_HAL if WITH_UDEV -libvirt_driver_nodedev_la_SOURCES += $(NODE_DEVICE_DRIVER_UDEV_SOURCES) -libvirt_driver_nodedev_la_CFLAGS += \ +libvirt_driver_nodedev_impl_la_SOURCES += $(NODE_DEVICE_DRIVER_UDEV_SOURCES) +libvirt_driver_nodedev_impl_la_CFLAGS += \ $(UDEV_CFLAGS) \ $(PCIACCESS_CFLAGS) \ $(NULL) -libvirt_driver_nodedev_la_LIBADD += \ +libvirt_driver_nodedev_impl_la_LIBADD += \ $(UDEV_LIBS) \ $(PCIACCESS_LIBS) \ $(NULL) -- 2.21.3

On Tue, Jun 09, 2020 at 04:43:46PM -0500, Jonathon Jongsma wrote:
In order to test the nodedev driver, we need to link against a non-loadable module. Similar to other loadable modules already in the repository, create an _impl library that can be linked against the unit tests and then create a loadable module from that.
Signed-off-by: Jonathon Jongsma <jjongsma@redhat.com> --- Reviewed-by: Erik Skultety <eskultet@redhat.com>

Test that we run 'mdevctl' with the proper arguments when creating new mediated devices with virNodeDeviceCreateXML(). Signed-off-by: Jonathon Jongsma <jjongsma@redhat.com> --- build-aux/syntax-check.mk | 2 +- tests/Makefile.am | 14 + ...019_36ea_4111_8f0a_8c9a70e21366-start.argv | 1 + ...019_36ea_4111_8f0a_8c9a70e21366-start.json | 1 + ...d39_495e_4243_ad9f_beb3f14c23d9-start.argv | 1 + ...d39_495e_4243_ad9f_beb3f14c23d9-start.json | 1 + ...916_1ca8_49ac_b176_871d16c13076-start.argv | 1 + ...916_1ca8_49ac_b176_871d16c13076-start.json | 1 + tests/nodedevmdevctltest.c | 257 ++++++++++++++++++ ...v_d069d019_36ea_4111_8f0a_8c9a70e21366.xml | 8 + ...v_d2441d39_495e_4243_ad9f_beb3f14c23d9.xml | 10 + ...v_fedc4916_1ca8_49ac_b176_871d16c13076.xml | 9 + 12 files changed, 305 insertions(+), 1 deletion(-) create mode 100644 tests/nodedevmdevctldata/mdev_d069d019_36ea_4111_8f0a_8c9a70e21366-start.argv create mode 100644 tests/nodedevmdevctldata/mdev_d069d019_36ea_4111_8f0a_8c9a70e21366-start.json create mode 100644 tests/nodedevmdevctldata/mdev_d2441d39_495e_4243_ad9f_beb3f14c23d9-start.argv create mode 100644 tests/nodedevmdevctldata/mdev_d2441d39_495e_4243_ad9f_beb3f14c23d9-start.json create mode 100644 tests/nodedevmdevctldata/mdev_fedc4916_1ca8_49ac_b176_871d16c13076-start.argv create mode 100644 tests/nodedevmdevctldata/mdev_fedc4916_1ca8_49ac_b176_871d16c13076-start.json create mode 100644 tests/nodedevmdevctltest.c create mode 100644 tests/nodedevschemadata/mdev_d069d019_36ea_4111_8f0a_8c9a70e21366.xml create mode 100644 tests/nodedevschemadata/mdev_d2441d39_495e_4243_ad9f_beb3f14c23d9.xml create mode 100644 tests/nodedevschemadata/mdev_fedc4916_1ca8_49ac_b176_871d16c13076.xml diff --git a/build-aux/syntax-check.mk b/build-aux/syntax-check.mk index bf8832a2a5..d47a92b530 100644 --- a/build-aux/syntax-check.mk +++ b/build-aux/syntax-check.mk @@ -2015,7 +2015,7 @@ exclude_file_name_regexp--sc_prohibit_close = \ (\.p[yl]$$|\.spec\.in$$|^docs/|^(src/util/vir(file|event)\.c|src/libvirt-stream\.c|tests/(vir.+mock\.c|commandhelper\.c|qemusecuritymock\.c)|tools/nss/libvirt_nss_(leases|macs)\.c)$$) exclude_file_name_regexp--sc_prohibit_empty_lines_at_EOF = \ - (^tests/(virhostcpu|virpcitest)data/|docs/js/.*\.js|docs/fonts/.*\.woff|\.diff|tests/virconfdata/no-newline\.conf$$) + (^tests/(nodedevmdevctl|virhostcpu|virpcitest)data/|docs/js/.*\.js|docs/fonts/.*\.woff|\.diff|tests/virconfdata/no-newline\.conf$$) exclude_file_name_regexp--sc_prohibit_fork_wrappers = \ (^(src/(util/(vircommand|virdaemon)|lxc/lxc_controller)|tests/testutils)\.c$$) diff --git a/tests/Makefile.am b/tests/Makefile.am index f5766a7790..13cbdbb31e 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -388,6 +388,10 @@ test_programs += storagevolxml2xmltest test_programs += nodedevxml2xmltest +if WITH_NODE_DEVICES +test_programs += nodedevmdevctltest +endif WITH_NODE_DEVICES + test_programs += interfacexml2xmltest test_programs += cputest @@ -970,6 +974,16 @@ nodedevxml2xmltest_SOURCES = \ testutils.c testutils.h nodedevxml2xmltest_LDADD = $(LDADDS) +if WITH_NODE_DEVICES +nodedevmdevctltest_SOURCES = \ + nodedevmdevctltest.c \ + testutils.c testutils.h + +nodedevmdevctltest_LDADD = \ + ../src/libvirt_driver_nodedev_impl.la \ + $(LDADDS) +endif WITH_NODE_DEVICES + interfacexml2xmltest_SOURCES = \ interfacexml2xmltest.c \ testutils.c testutils.h diff --git a/tests/nodedevmdevctldata/mdev_d069d019_36ea_4111_8f0a_8c9a70e21366-start.argv b/tests/nodedevmdevctldata/mdev_d069d019_36ea_4111_8f0a_8c9a70e21366-start.argv new file mode 100644 index 0000000000..dae6dedf7f --- /dev/null +++ b/tests/nodedevmdevctldata/mdev_d069d019_36ea_4111_8f0a_8c9a70e21366-start.argv @@ -0,0 +1 @@ +/usr/sbin/mdevctl start -p 0000:00:02.0 --jsonfile /dev/stdin diff --git a/tests/nodedevmdevctldata/mdev_d069d019_36ea_4111_8f0a_8c9a70e21366-start.json b/tests/nodedevmdevctldata/mdev_d069d019_36ea_4111_8f0a_8c9a70e21366-start.json new file mode 100644 index 0000000000..bfc6dcace3 --- /dev/null +++ b/tests/nodedevmdevctldata/mdev_d069d019_36ea_4111_8f0a_8c9a70e21366-start.json @@ -0,0 +1 @@ +{"mdev_type":"i915-GVTg_V5_8","start":"manual"} \ No newline at end of file diff --git a/tests/nodedevmdevctldata/mdev_d2441d39_495e_4243_ad9f_beb3f14c23d9-start.argv b/tests/nodedevmdevctldata/mdev_d2441d39_495e_4243_ad9f_beb3f14c23d9-start.argv new file mode 100644 index 0000000000..dae6dedf7f --- /dev/null +++ b/tests/nodedevmdevctldata/mdev_d2441d39_495e_4243_ad9f_beb3f14c23d9-start.argv @@ -0,0 +1 @@ +/usr/sbin/mdevctl start -p 0000:00:02.0 --jsonfile /dev/stdin diff --git a/tests/nodedevmdevctldata/mdev_d2441d39_495e_4243_ad9f_beb3f14c23d9-start.json b/tests/nodedevmdevctldata/mdev_d2441d39_495e_4243_ad9f_beb3f14c23d9-start.json new file mode 100644 index 0000000000..e5b22b2c44 --- /dev/null +++ b/tests/nodedevmdevctldata/mdev_d2441d39_495e_4243_ad9f_beb3f14c23d9-start.json @@ -0,0 +1 @@ +{"mdev_type":"i915-GVTg_V5_8","start":"manual","attrs":[{"example-attribute-1":"attribute-value-1"},{"example-attribute-2":"attribute-value-2"}]} \ No newline at end of file diff --git a/tests/nodedevmdevctldata/mdev_fedc4916_1ca8_49ac_b176_871d16c13076-start.argv b/tests/nodedevmdevctldata/mdev_fedc4916_1ca8_49ac_b176_871d16c13076-start.argv new file mode 100644 index 0000000000..dae6dedf7f --- /dev/null +++ b/tests/nodedevmdevctldata/mdev_fedc4916_1ca8_49ac_b176_871d16c13076-start.argv @@ -0,0 +1 @@ +/usr/sbin/mdevctl start -p 0000:00:02.0 --jsonfile /dev/stdin diff --git a/tests/nodedevmdevctldata/mdev_fedc4916_1ca8_49ac_b176_871d16c13076-start.json b/tests/nodedevmdevctldata/mdev_fedc4916_1ca8_49ac_b176_871d16c13076-start.json new file mode 100644 index 0000000000..2e03d0bd8e --- /dev/null +++ b/tests/nodedevmdevctldata/mdev_fedc4916_1ca8_49ac_b176_871d16c13076-start.json @@ -0,0 +1 @@ +{"mdev_type":"i915-GVTg_V5_8","start":"manual","attrs":[{"example-attribute":"attribute-value"}]} \ No newline at end of file diff --git a/tests/nodedevmdevctltest.c b/tests/nodedevmdevctltest.c new file mode 100644 index 0000000000..32a22246c2 --- /dev/null +++ b/tests/nodedevmdevctltest.c @@ -0,0 +1,257 @@ +#include <config.h> + +#include "internal.h" +#include "testutils.h" +#include "datatypes.h" +#include "node_device/node_device_driver.h" +#include "vircommand.h" +#define LIBVIRT_VIRCOMMANDPRIV_H_ALLOW +#include "vircommandpriv.h" + +#define VIR_FROM_THIS VIR_FROM_NODEDEV + +struct testInfo { + bool shouldFail; + const char *virt_type; + int create; + const char *filename; +}; + +// capture stdin passed to command +static void +testCommandDryRunCallback(const char *const*args G_GNUC_UNUSED, + const char *const*env G_GNUC_UNUSED, + const char *input, + char **output G_GNUC_UNUSED, + char **error G_GNUC_UNUSED, + int *status G_GNUC_UNUSED, + void *opaque G_GNUC_UNUSED) +{ + char **stdinbuf = opaque; + + *stdinbuf = g_strdup(input); +} + +static int +testMdevctlStart(bool shouldFail G_GNUC_UNUSED, + const char *virt_type, + int create, + const char *mdevxml, + const char *startcmdfile, + const char *startjsonfile) +{ + virNodeDeviceDefPtr def = NULL; + virNodeDeviceObjPtr obj = NULL; + virBuffer buf = VIR_BUFFER_INITIALIZER; + const char *actualCmdline = NULL; + int ret = -1; + g_autofree char *uuid = NULL; + g_autofree char *stdinbuf = NULL; + g_autoptr(virCommand) cmd = NULL; + + if (!(def = virNodeDeviceDefParseFile(mdevxml, create, virt_type))) + goto cleanup; + + // this function will set a stdin buffer containing the json configuration + // of the device. This json value is captured in the mock wrapper above + cmd = nodeDeviceGetMdevctlStartCommand(def, false, &uuid); + + if (!cmd) + goto cleanup; + + virCommandSetDryRun(&buf, testCommandDryRunCallback, &stdinbuf); + if (virCommandRun(cmd, NULL) < 0) + goto cleanup; + + if (!(actualCmdline = virBufferCurrentContent(&buf))) + goto cleanup; + + if (virTestCompareToFile(actualCmdline, startcmdfile) < 0) + goto cleanup; + + if (virTestCompareToFile(stdinbuf, startjsonfile) < 0) + goto cleanup; + + ret = 0; + + cleanup: + virBufferFreeAndReset(&buf); + virCommandSetDryRun(NULL, NULL, NULL); + virNodeDeviceObjEndAPI(&obj); + return ret; +} + +static int +testMdevctlStartHelper(const void *data) +{ + const struct testInfo *info = data; + + g_autofree char *mdevxml = g_strdup_printf("%s/nodedevschemadata/%s.xml", + abs_srcdir, info->filename); + g_autofree char *cmdlinefile = g_strdup_printf("%s/nodedevmdevctldata/%s-start.argv", + abs_srcdir, info->filename); + g_autofree char *jsonfile = g_strdup_printf("%s/nodedevmdevctldata/%s-start.json", + abs_srcdir, info->filename); + + return testMdevctlStart(info->shouldFail, info->virt_type, + info->create, mdevxml, cmdlinefile, + jsonfile); +} + +static void +nodedevTestDriverFree(virNodeDeviceDriverStatePtr drv) +{ + if (!drv) + return; + + virCondDestroy(&drv->initCond); + virMutexDestroy(&drv->lock); + VIR_FREE(drv->stateDir); + VIR_FREE(drv); +} + +/* Add a fake root 'computer' device */ +static virNodeDeviceDefPtr +fakeRootDevice(void) +{ + virNodeDeviceDefPtr def = NULL; + + if (VIR_ALLOC(def) != 0 || VIR_ALLOC(def->caps) != 0) { + virNodeDeviceDefFree(def); + return NULL; + } + + def->name = g_strdup("computer"); + + return def; +} + +/* Add a fake pci device that can be used as a parent device for mediated + * devices. For our purposes, it only needs to have a name that matches the + * parent of the mdev, and it needs a PCI address + */ +static virNodeDeviceDefPtr +fakeParentDevice(void) +{ + virNodeDeviceDefPtr def = NULL; + virNodeDevCapPCIDevPtr pci_dev; + + if (VIR_ALLOC(def) != 0 || VIR_ALLOC(def->caps) != 0) { + virNodeDeviceDefFree(def); + return NULL; + } + + def->name = g_strdup("pci_0000_00_02_0"); + def->parent = g_strdup("computer"); + + def->caps->data.type = VIR_NODE_DEV_CAP_PCI_DEV; + pci_dev = &def->caps->data.pci_dev; + pci_dev->domain = 0; + pci_dev->bus = 0; + pci_dev->slot = 2; + pci_dev->function = 0; + + return def; +} + +static int +addDevice(virNodeDeviceDefPtr def) +{ + if (!def) + return -1; + + virNodeDeviceObjPtr obj = virNodeDeviceObjListAssignDef(driver->devs, def); + + if (!obj) { + virNodeDeviceDefFree(def); + return -1; + } + + virNodeDeviceObjEndAPI(&obj); + return 0; +} + +static int +nodedevTestDriverAddTestDevices(void) +{ + if (addDevice(fakeRootDevice()) < 0 || + addDevice(fakeParentDevice()) < 0) + return -1; + + return 0; +} + +/* Bare minimum driver init to be able to test nodedev functionality */ +static int +nodedevTestDriverInit(void) +{ + int ret = -1; + char statedir[] = abs_builddir "/nodedevstatedir-XXXXXX"; + if (VIR_ALLOC(driver) < 0) + return -1; + + if (!g_mkdtemp(statedir)) { + fprintf(stderr, "Cannot create fake stateDir"); + goto error; + } + + driver->stateDir = g_strdup(statedir); + driver->lockFD = -1; + if (virMutexInit(&driver->lock) < 0 || + virCondInit(&driver->initCond) < 0) { + VIR_TEST_DEBUG("Unable to initialize test nodedev driver"); + goto error; + } + + if (!(driver->devs = virNodeDeviceObjListNew())) + goto error; + + return 0; + + error: + nodedevTestDriverFree(driver); + return ret; +} + +static int +mymain(void) +{ + int ret = 0; + + if (nodedevTestDriverInit() < 0) + return EXIT_FAILURE; + + if (nodedevTestDriverAddTestDevices() < 0) { + ret = EXIT_FAILURE; + goto done; + } + + // add a mock device to the device list so it can be used as a parent + // reference + +#define DO_TEST_FULL(shouldFail, virt_type, create, filename) \ + do { \ + struct testInfo info = { shouldFail, virt_type, create, filename, }; \ + if (virTestRun("mdevctl start " filename, \ + testMdevctlStartHelper, &info) < 0) \ + ret = -1; \ + } \ + while (0); + +#define DO_TEST(...) \ + DO_TEST_FULL(false, "QEMU", CREATE_DEVICE, __VA_ARGS__) +#define DO_TEST_FAIL(...) \ + DO_TEST_FULL(true, "QEMU", CREATE_DEVICE, __VA_ARGS__) + + // Test mdevctl start commands + DO_TEST("mdev_d069d019_36ea_4111_8f0a_8c9a70e21366"); + DO_TEST("mdev_fedc4916_1ca8_49ac_b176_871d16c13076"); + DO_TEST("mdev_d2441d39_495e_4243_ad9f_beb3f14c23d9"); + + done: + nodedevTestDriverFree(driver); + + return ret == 0 ? EXIT_SUCCESS : EXIT_FAILURE; +} + +VIR_TEST_MAIN(mymain) diff --git a/tests/nodedevschemadata/mdev_d069d019_36ea_4111_8f0a_8c9a70e21366.xml b/tests/nodedevschemadata/mdev_d069d019_36ea_4111_8f0a_8c9a70e21366.xml new file mode 100644 index 0000000000..36d50630e4 --- /dev/null +++ b/tests/nodedevschemadata/mdev_d069d019_36ea_4111_8f0a_8c9a70e21366.xml @@ -0,0 +1,8 @@ +<device> + <name>mdev_d069d019_36ea_4111_8f0a_8c9a70e21366</name> + <parent>pci_0000_00_02_0</parent> + <capability type='mdev'> + <type id='i915-GVTg_V5_8'/> + <iommuGroup number='0'/> + </capability> +</device> diff --git a/tests/nodedevschemadata/mdev_d2441d39_495e_4243_ad9f_beb3f14c23d9.xml b/tests/nodedevschemadata/mdev_d2441d39_495e_4243_ad9f_beb3f14c23d9.xml new file mode 100644 index 0000000000..1aa0afe085 --- /dev/null +++ b/tests/nodedevschemadata/mdev_d2441d39_495e_4243_ad9f_beb3f14c23d9.xml @@ -0,0 +1,10 @@ +<device> + <name>mdev_d2441d39_495e_4243_ad9f_beb3f14c23d9</name> + <parent>pci_0000_00_02_0</parent> + <capability type='mdev'> + <type id='i915-GVTg_V5_8'/> + <iommuGroup number='0'/> + <attr name='example-attribute-1' value='attribute-value-1'/> + <attr name='example-attribute-2' value='attribute-value-2'/> + </capability> +</device> diff --git a/tests/nodedevschemadata/mdev_fedc4916_1ca8_49ac_b176_871d16c13076.xml b/tests/nodedevschemadata/mdev_fedc4916_1ca8_49ac_b176_871d16c13076.xml new file mode 100644 index 0000000000..da2a702304 --- /dev/null +++ b/tests/nodedevschemadata/mdev_fedc4916_1ca8_49ac_b176_871d16c13076.xml @@ -0,0 +1,9 @@ +<device> + <name>mdev_fedc4916_1ca8_49ac_b176_871d16c13076</name> + <parent>pci_0000_00_02_0</parent> + <capability type='mdev'> + <type id='i915-GVTg_V5_8'/> + <iommuGroup number='0'/> + <attr name='example-attribute' value='attribute-value'/> + </capability> +</device> -- 2.21.3

On 6/9/20 11:43 PM, Jonathon Jongsma wrote:
Test that we run 'mdevctl' with the proper arguments when creating new mediated devices with virNodeDeviceCreateXML().
Signed-off-by: Jonathon Jongsma <jjongsma@redhat.com> --- build-aux/syntax-check.mk | 2 +- tests/Makefile.am | 14 + ...019_36ea_4111_8f0a_8c9a70e21366-start.argv | 1 + ...019_36ea_4111_8f0a_8c9a70e21366-start.json | 1 + ...d39_495e_4243_ad9f_beb3f14c23d9-start.argv | 1 + ...d39_495e_4243_ad9f_beb3f14c23d9-start.json | 1 + ...916_1ca8_49ac_b176_871d16c13076-start.argv | 1 + ...916_1ca8_49ac_b176_871d16c13076-start.json | 1 + tests/nodedevmdevctltest.c | 257 ++++++++++++++++++ ...v_d069d019_36ea_4111_8f0a_8c9a70e21366.xml | 8 + ...v_d2441d39_495e_4243_ad9f_beb3f14c23d9.xml | 10 + ...v_fedc4916_1ca8_49ac_b176_871d16c13076.xml | 9 + 12 files changed, 305 insertions(+), 1 deletion(-) create mode 100644 tests/nodedevmdevctldata/mdev_d069d019_36ea_4111_8f0a_8c9a70e21366-start.argv create mode 100644 tests/nodedevmdevctldata/mdev_d069d019_36ea_4111_8f0a_8c9a70e21366-start.json create mode 100644 tests/nodedevmdevctldata/mdev_d2441d39_495e_4243_ad9f_beb3f14c23d9-start.argv create mode 100644 tests/nodedevmdevctldata/mdev_d2441d39_495e_4243_ad9f_beb3f14c23d9-start.json create mode 100644 tests/nodedevmdevctldata/mdev_fedc4916_1ca8_49ac_b176_871d16c13076-start.argv create mode 100644 tests/nodedevmdevctldata/mdev_fedc4916_1ca8_49ac_b176_871d16c13076-start.json create mode 100644 tests/nodedevmdevctltest.c create mode 100644 tests/nodedevschemadata/mdev_d069d019_36ea_4111_8f0a_8c9a70e21366.xml create mode 100644 tests/nodedevschemadata/mdev_d2441d39_495e_4243_ad9f_beb3f14c23d9.xml create mode 100644 tests/nodedevschemadata/mdev_fedc4916_1ca8_49ac_b176_871d16c13076.xml
diff --git a/build-aux/syntax-check.mk b/build-aux/syntax-check.mk index bf8832a2a5..d47a92b530 100644 --- a/build-aux/syntax-check.mk +++ b/build-aux/syntax-check.mk @@ -2015,7 +2015,7 @@ exclude_file_name_regexp--sc_prohibit_close = \ (\.p[yl]$$|\.spec\.in$$|^docs/|^(src/util/vir(file|event)\.c|src/libvirt-stream\.c|tests/(vir.+mock\.c|commandhelper\.c|qemusecuritymock\.c)|tools/nss/libvirt_nss_(leases|macs)\.c)$$)
exclude_file_name_regexp--sc_prohibit_empty_lines_at_EOF = \ - (^tests/(virhostcpu|virpcitest)data/|docs/js/.*\.js|docs/fonts/.*\.woff|\.diff|tests/virconfdata/no-newline\.conf$$) + (^tests/(nodedevmdevctl|virhostcpu|virpcitest)data/|docs/js/.*\.js|docs/fonts/.*\.woff|\.diff|tests/virconfdata/no-newline\.conf$$)
exclude_file_name_regexp--sc_prohibit_fork_wrappers = \ (^(src/(util/(vircommand|virdaemon)|lxc/lxc_controller)|tests/testutils)\.c$$) diff --git a/tests/Makefile.am b/tests/Makefile.am index f5766a7790..13cbdbb31e 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -388,6 +388,10 @@ test_programs += storagevolxml2xmltest
test_programs += nodedevxml2xmltest
+if WITH_NODE_DEVICES +test_programs += nodedevmdevctltest +endif WITH_NODE_DEVICES + test_programs += interfacexml2xmltest
test_programs += cputest @@ -970,6 +974,16 @@ nodedevxml2xmltest_SOURCES = \ testutils.c testutils.h nodedevxml2xmltest_LDADD = $(LDADDS)
+if WITH_NODE_DEVICES +nodedevmdevctltest_SOURCES = \ + nodedevmdevctltest.c \ + testutils.c testutils.h + +nodedevmdevctltest_LDADD = \ + ../src/libvirt_driver_nodedev_impl.la \ + $(LDADDS) +endif WITH_NODE_DEVICES + interfacexml2xmltest_SOURCES = \ interfacexml2xmltest.c \ testutils.c testutils.h diff --git a/tests/nodedevmdevctldata/mdev_d069d019_36ea_4111_8f0a_8c9a70e21366-start.argv b/tests/nodedevmdevctldata/mdev_d069d019_36ea_4111_8f0a_8c9a70e21366-start.argv new file mode 100644 index 0000000000..dae6dedf7f --- /dev/null +++ b/tests/nodedevmdevctldata/mdev_d069d019_36ea_4111_8f0a_8c9a70e21366-start.argv @@ -0,0 +1 @@ +/usr/sbin/mdevctl start -p 0000:00:02.0 --jsonfile /dev/stdin
This is a problem. If there is no mdevctl found during configure (my case), then MDEVCTL is going to be plain "mdevctl". Which is okay for the nodedev driver, because it uses virCommandRun() which uses virFindFileInPath() to get the real path at runtime. But for tests it won't fly. Alternativelly, if the mdevctl lived under say /bin or any other location than /usr/sbin the expected output would be different. In virnetdevbandwidthtest.c (which uses TC) I've worked around this by using TC directly to construct expected output. However, I guess we can safely assume that either mdevctl is present at build time so that its location is detected properly, or we hardcode its path. IOW, this needs to be squashed into 05/10: diff --git i/m4/virt-external-programs.m4 w/m4/virt-external-programs.m4 index bd3cb1f757..f642dcbf0e 100644 --- i/m4/virt-external-programs.m4 +++ w/m4/virt-external-programs.m4 @@ -65,7 +65,7 @@ AC_DEFUN([LIBVIRT_CHECK_EXTERNAL_PROGRAMS], [ AC_PATH_PROG([OVSVSCTL], [ovs-vsctl], [ovs-vsctl], [$LIBVIRT_SBIN_PATH]) AC_PATH_PROG([SCRUB], [scrub], [scrub], [$LIBVIRT_SBIN_PATH]) AC_PATH_PROG([ADDR2LINE], [addr2line], [addr2line], [$LIBVIRT_SBIN_PATH]) - AC_PATH_PROG([MDEVCTL], [mdevctl], [mdevctl], [$LIBVIRT_SBIN_PATH]) + AC_PATH_PROG([MDEVCTL], [mdevctl], [/usr/sbin/mdevctl], [$LIBVIRT_SBIN_PATH]) AC_DEFINE_UNQUOTED([DMIDECODE], ["$DMIDECODE"], [Location or name of the dmidecode program])
diff --git a/tests/nodedevmdevctldata/mdev_d069d019_36ea_4111_8f0a_8c9a70e21366-start.json b/tests/nodedevmdevctldata/mdev_d069d019_36ea_4111_8f0a_8c9a70e21366-start.json new file mode 100644 index 0000000000..bfc6dcace3 --- /dev/null +++ b/tests/nodedevmdevctldata/mdev_d069d019_36ea_4111_8f0a_8c9a70e21366-start.json @@ -0,0 +1 @@ +{"mdev_type":"i915-GVTg_V5_8","start":"manual"} \ No newline at end of file diff --git a/tests/nodedevmdevctldata/mdev_d2441d39_495e_4243_ad9f_beb3f14c23d9-start.argv b/tests/nodedevmdevctldata/mdev_d2441d39_495e_4243_ad9f_beb3f14c23d9-start.argv new file mode 100644 index 0000000000..dae6dedf7f --- /dev/null +++ b/tests/nodedevmdevctldata/mdev_d2441d39_495e_4243_ad9f_beb3f14c23d9-start.argv @@ -0,0 +1 @@ +/usr/sbin/mdevctl start -p 0000:00:02.0 --jsonfile /dev/stdin diff --git a/tests/nodedevmdevctldata/mdev_d2441d39_495e_4243_ad9f_beb3f14c23d9-start.json b/tests/nodedevmdevctldata/mdev_d2441d39_495e_4243_ad9f_beb3f14c23d9-start.json new file mode 100644 index 0000000000..e5b22b2c44 --- /dev/null +++ b/tests/nodedevmdevctldata/mdev_d2441d39_495e_4243_ad9f_beb3f14c23d9-start.json @@ -0,0 +1 @@ +{"mdev_type":"i915-GVTg_V5_8","start":"manual","attrs":[{"example-attribute-1":"attribute-value-1"},{"example-attribute-2":"attribute-value-2"}]} \ No newline at end of file diff --git a/tests/nodedevmdevctldata/mdev_fedc4916_1ca8_49ac_b176_871d16c13076-start.argv b/tests/nodedevmdevctldata/mdev_fedc4916_1ca8_49ac_b176_871d16c13076-start.argv new file mode 100644 index 0000000000..dae6dedf7f --- /dev/null +++ b/tests/nodedevmdevctldata/mdev_fedc4916_1ca8_49ac_b176_871d16c13076-start.argv @@ -0,0 +1 @@ +/usr/sbin/mdevctl start -p 0000:00:02.0 --jsonfile /dev/stdin diff --git a/tests/nodedevmdevctldata/mdev_fedc4916_1ca8_49ac_b176_871d16c13076-start.json b/tests/nodedevmdevctldata/mdev_fedc4916_1ca8_49ac_b176_871d16c13076-start.json new file mode 100644 index 0000000000..2e03d0bd8e --- /dev/null +++ b/tests/nodedevmdevctldata/mdev_fedc4916_1ca8_49ac_b176_871d16c13076-start.json @@ -0,0 +1 @@ +{"mdev_type":"i915-GVTg_V5_8","start":"manual","attrs":[{"example-attribute":"attribute-value"}]} \ No newline at end of file diff --git a/tests/nodedevmdevctltest.c b/tests/nodedevmdevctltest.c new file mode 100644 index 0000000000..32a22246c2 --- /dev/null +++ b/tests/nodedevmdevctltest.c @@ -0,0 +1,257 @@ +#include <config.h> + +#include "internal.h" +#include "testutils.h" +#include "datatypes.h" +#include "node_device/node_device_driver.h" +#include "vircommand.h" +#define LIBVIRT_VIRCOMMANDPRIV_H_ALLOW +#include "vircommandpriv.h" + +#define VIR_FROM_THIS VIR_FROM_NODEDEV + +struct testInfo { + bool shouldFail; + const char *virt_type; + int create; + const char *filename; +}; + +// capture stdin passed to command +static void +testCommandDryRunCallback(const char *const*args G_GNUC_UNUSED, + const char *const*env G_GNUC_UNUSED, + const char *input, + char **output G_GNUC_UNUSED, + char **error G_GNUC_UNUSED, + int *status G_GNUC_UNUSED, + void *opaque G_GNUC_UNUSED) +{ + char **stdinbuf = opaque; + + *stdinbuf = g_strdup(input); +} + +static int +testMdevctlStart(bool shouldFail G_GNUC_UNUSED, + const char *virt_type, + int create, + const char *mdevxml, + const char *startcmdfile, + const char *startjsonfile) +{ + virNodeDeviceDefPtr def = NULL;
@def is never freed.
+ virNodeDeviceObjPtr obj = NULL; + virBuffer buf = VIR_BUFFER_INITIALIZER; + const char *actualCmdline = NULL; + int ret = -1; + g_autofree char *uuid = NULL; + g_autofree char *stdinbuf = NULL; + g_autoptr(virCommand) cmd = NULL; + + if (!(def = virNodeDeviceDefParseFile(mdevxml, create, virt_type))) + goto cleanup; + + // this function will set a stdin buffer containing the json configuration + // of the device. This json value is captured in the mock wrapper above + cmd = nodeDeviceGetMdevctlStartCommand(def, false, &uuid); + + if (!cmd) + goto cleanup; + + virCommandSetDryRun(&buf, testCommandDryRunCallback, &stdinbuf); + if (virCommandRun(cmd, NULL) < 0) + goto cleanup; + + if (!(actualCmdline = virBufferCurrentContent(&buf))) + goto cleanup; + + if (virTestCompareToFile(actualCmdline, startcmdfile) < 0) + goto cleanup; + + if (virTestCompareToFile(stdinbuf, startjsonfile) < 0) + goto cleanup; + + ret = 0; + + cleanup: + virBufferFreeAndReset(&buf); + virCommandSetDryRun(NULL, NULL, NULL); + virNodeDeviceObjEndAPI(&obj); + return ret; +} + +static int +testMdevctlStartHelper(const void *data) +{ + const struct testInfo *info = data; + + g_autofree char *mdevxml = g_strdup_printf("%s/nodedevschemadata/%s.xml", + abs_srcdir, info->filename); + g_autofree char *cmdlinefile = g_strdup_printf("%s/nodedevmdevctldata/%s-start.argv", + abs_srcdir, info->filename); + g_autofree char *jsonfile = g_strdup_printf("%s/nodedevmdevctldata/%s-start.json", + abs_srcdir, info->filename); + + return testMdevctlStart(info->shouldFail, info->virt_type, + info->create, mdevxml, cmdlinefile, + jsonfile); +} + +static void +nodedevTestDriverFree(virNodeDeviceDriverStatePtr drv) +{ + if (!drv) + return; +
All devices added in nodedevTestDriverAddTestDevices() should be freed here.
+ virCondDestroy(&drv->initCond); + virMutexDestroy(&drv->lock); + VIR_FREE(drv->stateDir); + VIR_FREE(drv); +} + +/* Add a fake root 'computer' device */ +static virNodeDeviceDefPtr +fakeRootDevice(void) +{ + virNodeDeviceDefPtr def = NULL; + + if (VIR_ALLOC(def) != 0 || VIR_ALLOC(def->caps) != 0) { + virNodeDeviceDefFree(def); + return NULL; + } + + def->name = g_strdup("computer"); + + return def; +} + +/* Add a fake pci device that can be used as a parent device for mediated + * devices. For our purposes, it only needs to have a name that matches the + * parent of the mdev, and it needs a PCI address + */ +static virNodeDeviceDefPtr +fakeParentDevice(void) +{ + virNodeDeviceDefPtr def = NULL; + virNodeDevCapPCIDevPtr pci_dev; + + if (VIR_ALLOC(def) != 0 || VIR_ALLOC(def->caps) != 0) { + virNodeDeviceDefFree(def); + return NULL; + } + + def->name = g_strdup("pci_0000_00_02_0"); + def->parent = g_strdup("computer"); + + def->caps->data.type = VIR_NODE_DEV_CAP_PCI_DEV; + pci_dev = &def->caps->data.pci_dev; + pci_dev->domain = 0; + pci_dev->bus = 0; + pci_dev->slot = 2; + pci_dev->function = 0; + + return def; +} + +static int +addDevice(virNodeDeviceDefPtr def) +{ + if (!def) + return -1; + + virNodeDeviceObjPtr obj = virNodeDeviceObjListAssignDef(driver->devs, def); + + if (!obj) { + virNodeDeviceDefFree(def); + return -1; + } + + virNodeDeviceObjEndAPI(&obj); + return 0; +} + +static int +nodedevTestDriverAddTestDevices(void) +{ + if (addDevice(fakeRootDevice()) < 0 || + addDevice(fakeParentDevice()) < 0) + return -1; + + return 0; +} + +/* Bare minimum driver init to be able to test nodedev functionality */ +static int +nodedevTestDriverInit(void) +{ + int ret = -1; + char statedir[] = abs_builddir "/nodedevstatedir-XXXXXX"; + if (VIR_ALLOC(driver) < 0) + return -1; + + if (!g_mkdtemp(statedir)) { + fprintf(stderr, "Cannot create fake stateDir"); + goto error; + }
This creates a temporary dir which is never removed. But it doesn't look like the directory is needed at all, is it?
+ + driver->stateDir = g_strdup(statedir); + driver->lockFD = -1; + if (virMutexInit(&driver->lock) < 0 || + virCondInit(&driver->initCond) < 0) { + VIR_TEST_DEBUG("Unable to initialize test nodedev driver"); + goto error; + } + + if (!(driver->devs = virNodeDeviceObjListNew())) + goto error; + + return 0; + + error: + nodedevTestDriverFree(driver); + return ret; +}
The following should be squashed in: diff --git i/tests/nodedevmdevctltest.c w/tests/nodedevmdevctltest.c index 32a22246c2..e57eaa0cf2 100644 --- i/tests/nodedevmdevctltest.c +++ w/tests/nodedevmdevctltest.c @@ -40,7 +40,7 @@ testMdevctlStart(bool shouldFail G_GNUC_UNUSED, const char *startcmdfile, const char *startjsonfile) { - virNodeDeviceDefPtr def = NULL; + g_autoptr(virNodeDeviceDef) def = NULL; virNodeDeviceObjPtr obj = NULL; virBuffer buf = VIR_BUFFER_INITIALIZER; const char *actualCmdline = NULL; @@ -104,6 +104,7 @@ nodedevTestDriverFree(virNodeDeviceDriverStatePtr drv) if (!drv) return; + virNodeDeviceObjListFree(drv->devs); virCondDestroy(&drv->initCond); virMutexDestroy(&drv->lock); VIR_FREE(drv->stateDir); @@ -186,16 +187,10 @@ static int nodedevTestDriverInit(void) { int ret = -1; - char statedir[] = abs_builddir "/nodedevstatedir-XXXXXX"; + if (VIR_ALLOC(driver) < 0) return -1; - if (!g_mkdtemp(statedir)) { - fprintf(stderr, "Cannot create fake stateDir"); - goto error; - } - - driver->stateDir = g_strdup(statedir); driver->lockFD = -1; if (virMutexInit(&driver->lock) < 0 || virCondInit(&driver->initCond) < 0) { Michal

On Wed, 2020-06-10 at 20:01 +0200, Michal Privoznik wrote:
new file mode 100644 index 0000000000..dae6dedf7f --- /dev/null +++ b/tests/nodedevmdevctldata/mdev_d069d019_36ea_4111_8f0a_8c9a70e2136 6-start.argv @@ -0,0 +1 @@ +/usr/sbin/mdevctl start -p 0000:00:02.0 --jsonfile /dev/stdin
This is a problem. If there is no mdevctl found during configure (my case), then MDEVCTL is going to be plain "mdevctl". Which is okay for the nodedev driver, because it uses virCommandRun() which uses virFindFileInPath() to get the real path at runtime. But for tests it won't fly. Alternativelly, if the mdevctl lived under say /bin or any other location than /usr/sbin the expected output would be different.
In virnetdevbandwidthtest.c (which uses TC) I've worked around this by using TC directly to construct expected output. However, I guess we can safely assume that either mdevctl is present at build time so that its location is detected properly, or we hardcode its path. IOW, this needs to be squashed into 05/10:
diff --git i/m4/virt-external-programs.m4 w/m4/virt-external- programs.m4 index bd3cb1f757..f642dcbf0e 100644 --- i/m4/virt-external-programs.m4 +++ w/m4/virt-external-programs.m4 @@ -65,7 +65,7 @@ AC_DEFUN([LIBVIRT_CHECK_EXTERNAL_PROGRAMS], [ AC_PATH_PROG([OVSVSCTL], [ovs-vsctl], [ovs-vsctl], [$LIBVIRT_SBIN_PATH]) AC_PATH_PROG([SCRUB], [scrub], [scrub], [$LIBVIRT_SBIN_PATH]) AC_PATH_PROG([ADDR2LINE], [addr2line], [addr2line], [$LIBVIRT_SBIN_PATH]) - AC_PATH_PROG([MDEVCTL], [mdevctl], [mdevctl], [$LIBVIRT_SBIN_PATH]) + AC_PATH_PROG([MDEVCTL], [mdevctl], [/usr/sbin/mdevctl], [$LIBVIRT_SBIN_PATH])
AC_DEFINE_UNQUOTED([DMIDECODE], ["$DMIDECODE"], [Location or name of the dmidecode program])
That's a good point. But is this adequate? What if there's a distro that installs mdevctl into /usr/bin instead of /usr/sbin. Configure will find the /usr/bin/mdevctl binary and the test output will not match... How do we get around that issue for other argv tests? (e.g. qemu?) Or do we just assume that tests will fail on machines that install things in non-standard locations?
+/* Bare minimum driver init to be able to test nodedev functionality */ +static int +nodedevTestDriverInit(void) +{ + int ret = -1; + char statedir[] = abs_builddir "/nodedevstatedir-XXXXXX"; + if (VIR_ALLOC(driver) < 0) + return -1; + + if (!g_mkdtemp(statedir)) { + fprintf(stderr, "Cannot create fake stateDir"); + goto error; + }
This creates a temporary dir which is never removed. But it doesn't look like the directory is needed at all, is it?
Oh yes, this was leftover from the previous version of the patch where I created a temp file for the json config in the state dir. It can be dropped. Jonathon

On Wed, Jun 10, 2020 at 08:01:47PM +0200, Michal Privoznik wrote:
On 6/9/20 11:43 PM, Jonathon Jongsma wrote:
Test that we run 'mdevctl' with the proper arguments when creating new mediated devices with virNodeDeviceCreateXML().
Signed-off-by: Jonathon Jongsma <jjongsma@redhat.com> --- build-aux/syntax-check.mk | 2 +- tests/Makefile.am | 14 + ...019_36ea_4111_8f0a_8c9a70e21366-start.argv | 1 + ...019_36ea_4111_8f0a_8c9a70e21366-start.json | 1 + ...d39_495e_4243_ad9f_beb3f14c23d9-start.argv | 1 + ...d39_495e_4243_ad9f_beb3f14c23d9-start.json | 1 + ...916_1ca8_49ac_b176_871d16c13076-start.argv | 1 + ...916_1ca8_49ac_b176_871d16c13076-start.json | 1 + tests/nodedevmdevctltest.c | 257 ++++++++++++++++++ ...v_d069d019_36ea_4111_8f0a_8c9a70e21366.xml | 8 + ...v_d2441d39_495e_4243_ad9f_beb3f14c23d9.xml | 10 + ...v_fedc4916_1ca8_49ac_b176_871d16c13076.xml | 9 + 12 files changed, 305 insertions(+), 1 deletion(-) create mode 100644 tests/nodedevmdevctldata/mdev_d069d019_36ea_4111_8f0a_8c9a70e21366-start.argv create mode 100644 tests/nodedevmdevctldata/mdev_d069d019_36ea_4111_8f0a_8c9a70e21366-start.json create mode 100644 tests/nodedevmdevctldata/mdev_d2441d39_495e_4243_ad9f_beb3f14c23d9-start.argv create mode 100644 tests/nodedevmdevctldata/mdev_d2441d39_495e_4243_ad9f_beb3f14c23d9-start.json create mode 100644 tests/nodedevmdevctldata/mdev_fedc4916_1ca8_49ac_b176_871d16c13076-start.argv create mode 100644 tests/nodedevmdevctldata/mdev_fedc4916_1ca8_49ac_b176_871d16c13076-start.json create mode 100644 tests/nodedevmdevctltest.c create mode 100644 tests/nodedevschemadata/mdev_d069d019_36ea_4111_8f0a_8c9a70e21366.xml create mode 100644 tests/nodedevschemadata/mdev_d2441d39_495e_4243_ad9f_beb3f14c23d9.xml create mode 100644 tests/nodedevschemadata/mdev_fedc4916_1ca8_49ac_b176_871d16c13076.xml
diff --git a/build-aux/syntax-check.mk b/build-aux/syntax-check.mk index bf8832a2a5..d47a92b530 100644 --- a/build-aux/syntax-check.mk +++ b/build-aux/syntax-check.mk @@ -2015,7 +2015,7 @@ exclude_file_name_regexp--sc_prohibit_close = \ (\.p[yl]$$|\.spec\.in$$|^docs/|^(src/util/vir(file|event)\.c|src/libvirt-stream\.c|tests/(vir.+mock\.c|commandhelper\.c|qemusecuritymock\.c)|tools/nss/libvirt_nss_(leases|macs)\.c)$$) exclude_file_name_regexp--sc_prohibit_empty_lines_at_EOF = \ - (^tests/(virhostcpu|virpcitest)data/|docs/js/.*\.js|docs/fonts/.*\.woff|\.diff|tests/virconfdata/no-newline\.conf$$) + (^tests/(nodedevmdevctl|virhostcpu|virpcitest)data/|docs/js/.*\.js|docs/fonts/.*\.woff|\.diff|tests/virconfdata/no-newline\.conf$$) exclude_file_name_regexp--sc_prohibit_fork_wrappers = \ (^(src/(util/(vircommand|virdaemon)|lxc/lxc_controller)|tests/testutils)\.c$$) diff --git a/tests/Makefile.am b/tests/Makefile.am index f5766a7790..13cbdbb31e 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -388,6 +388,10 @@ test_programs += storagevolxml2xmltest test_programs += nodedevxml2xmltest +if WITH_NODE_DEVICES +test_programs += nodedevmdevctltest +endif WITH_NODE_DEVICES + test_programs += interfacexml2xmltest test_programs += cputest @@ -970,6 +974,16 @@ nodedevxml2xmltest_SOURCES = \ testutils.c testutils.h nodedevxml2xmltest_LDADD = $(LDADDS) +if WITH_NODE_DEVICES +nodedevmdevctltest_SOURCES = \ + nodedevmdevctltest.c \ + testutils.c testutils.h + +nodedevmdevctltest_LDADD = \ + ../src/libvirt_driver_nodedev_impl.la \ + $(LDADDS) +endif WITH_NODE_DEVICES + interfacexml2xmltest_SOURCES = \ interfacexml2xmltest.c \ testutils.c testutils.h diff --git a/tests/nodedevmdevctldata/mdev_d069d019_36ea_4111_8f0a_8c9a70e21366-start.argv b/tests/nodedevmdevctldata/mdev_d069d019_36ea_4111_8f0a_8c9a70e21366-start.argv new file mode 100644 index 0000000000..dae6dedf7f --- /dev/null +++ b/tests/nodedevmdevctldata/mdev_d069d019_36ea_4111_8f0a_8c9a70e21366-start.argv @@ -0,0 +1 @@ +/usr/sbin/mdevctl start -p 0000:00:02.0 --jsonfile /dev/stdin
This is a problem. If there is no mdevctl found during configure (my case), then MDEVCTL is going to be plain "mdevctl". Which is okay for the nodedev driver, because it uses virCommandRun() which uses virFindFileInPath() to get the real path at runtime. But for tests it won't fly. Alternativelly, if the mdevctl lived under say /bin or any other location than /usr/sbin the expected output would be different.
In virnetdevbandwidthtest.c (which uses TC) I've worked around this by using TC directly to construct expected output. However, I guess we can safely assume that either mdevctl is present at build time so that its location is
Why can we assume it safely? config.log doesn't complain and since we didn't put this in a BuildRequires stanza, even if you do dnf builddep, you won't get the program. Note that the situation with TC is different because (if I overlook gentoo), it is provided by iproute-tc on Fedora and iproute2 on Debian which are installed by default, so TC will be always present. I don't think we should silently assume anything here, I believe configure should complain because otherwise the tests will be failing and the developer would not know why and it would also be confusing since our gitlab pipelines would be green. Regards, Erik

On 6/11/20 3:40 PM, Erik Skultety wrote:
On Wed, Jun 10, 2020 at 08:01:47PM +0200, Michal Privoznik wrote:
On 6/9/20 11:43 PM, Jonathon Jongsma wrote:
Test that we run 'mdevctl' with the proper arguments when creating new mediated devices with virNodeDeviceCreateXML().
Signed-off-by: Jonathon Jongsma <jjongsma@redhat.com> --- build-aux/syntax-check.mk | 2 +- tests/Makefile.am | 14 + ...019_36ea_4111_8f0a_8c9a70e21366-start.argv | 1 + ...019_36ea_4111_8f0a_8c9a70e21366-start.json | 1 + ...d39_495e_4243_ad9f_beb3f14c23d9-start.argv | 1 + ...d39_495e_4243_ad9f_beb3f14c23d9-start.json | 1 + ...916_1ca8_49ac_b176_871d16c13076-start.argv | 1 + ...916_1ca8_49ac_b176_871d16c13076-start.json | 1 + tests/nodedevmdevctltest.c | 257 ++++++++++++++++++ ...v_d069d019_36ea_4111_8f0a_8c9a70e21366.xml | 8 + ...v_d2441d39_495e_4243_ad9f_beb3f14c23d9.xml | 10 + ...v_fedc4916_1ca8_49ac_b176_871d16c13076.xml | 9 + 12 files changed, 305 insertions(+), 1 deletion(-) create mode 100644 tests/nodedevmdevctldata/mdev_d069d019_36ea_4111_8f0a_8c9a70e21366-start.argv create mode 100644 tests/nodedevmdevctldata/mdev_d069d019_36ea_4111_8f0a_8c9a70e21366-start.json create mode 100644 tests/nodedevmdevctldata/mdev_d2441d39_495e_4243_ad9f_beb3f14c23d9-start.argv create mode 100644 tests/nodedevmdevctldata/mdev_d2441d39_495e_4243_ad9f_beb3f14c23d9-start.json create mode 100644 tests/nodedevmdevctldata/mdev_fedc4916_1ca8_49ac_b176_871d16c13076-start.argv create mode 100644 tests/nodedevmdevctldata/mdev_fedc4916_1ca8_49ac_b176_871d16c13076-start.json create mode 100644 tests/nodedevmdevctltest.c create mode 100644 tests/nodedevschemadata/mdev_d069d019_36ea_4111_8f0a_8c9a70e21366.xml create mode 100644 tests/nodedevschemadata/mdev_d2441d39_495e_4243_ad9f_beb3f14c23d9.xml create mode 100644 tests/nodedevschemadata/mdev_fedc4916_1ca8_49ac_b176_871d16c13076.xml
diff --git a/build-aux/syntax-check.mk b/build-aux/syntax-check.mk index bf8832a2a5..d47a92b530 100644 --- a/build-aux/syntax-check.mk +++ b/build-aux/syntax-check.mk @@ -2015,7 +2015,7 @@ exclude_file_name_regexp--sc_prohibit_close = \ (\.p[yl]$$|\.spec\.in$$|^docs/|^(src/util/vir(file|event)\.c|src/libvirt-stream\.c|tests/(vir.+mock\.c|commandhelper\.c|qemusecuritymock\.c)|tools/nss/libvirt_nss_(leases|macs)\.c)$$) exclude_file_name_regexp--sc_prohibit_empty_lines_at_EOF = \ - (^tests/(virhostcpu|virpcitest)data/|docs/js/.*\.js|docs/fonts/.*\.woff|\.diff|tests/virconfdata/no-newline\.conf$$) + (^tests/(nodedevmdevctl|virhostcpu|virpcitest)data/|docs/js/.*\.js|docs/fonts/.*\.woff|\.diff|tests/virconfdata/no-newline\.conf$$) exclude_file_name_regexp--sc_prohibit_fork_wrappers = \ (^(src/(util/(vircommand|virdaemon)|lxc/lxc_controller)|tests/testutils)\.c$$) diff --git a/tests/Makefile.am b/tests/Makefile.am index f5766a7790..13cbdbb31e 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -388,6 +388,10 @@ test_programs += storagevolxml2xmltest test_programs += nodedevxml2xmltest +if WITH_NODE_DEVICES +test_programs += nodedevmdevctltest +endif WITH_NODE_DEVICES + test_programs += interfacexml2xmltest test_programs += cputest @@ -970,6 +974,16 @@ nodedevxml2xmltest_SOURCES = \ testutils.c testutils.h nodedevxml2xmltest_LDADD = $(LDADDS) +if WITH_NODE_DEVICES +nodedevmdevctltest_SOURCES = \ + nodedevmdevctltest.c \ + testutils.c testutils.h + +nodedevmdevctltest_LDADD = \ + ../src/libvirt_driver_nodedev_impl.la \ + $(LDADDS) +endif WITH_NODE_DEVICES + interfacexml2xmltest_SOURCES = \ interfacexml2xmltest.c \ testutils.c testutils.h diff --git a/tests/nodedevmdevctldata/mdev_d069d019_36ea_4111_8f0a_8c9a70e21366-start.argv b/tests/nodedevmdevctldata/mdev_d069d019_36ea_4111_8f0a_8c9a70e21366-start.argv new file mode 100644 index 0000000000..dae6dedf7f --- /dev/null +++ b/tests/nodedevmdevctldata/mdev_d069d019_36ea_4111_8f0a_8c9a70e21366-start.argv @@ -0,0 +1 @@ +/usr/sbin/mdevctl start -p 0000:00:02.0 --jsonfile /dev/stdin
This is a problem. If there is no mdevctl found during configure (my case), then MDEVCTL is going to be plain "mdevctl". Which is okay for the nodedev driver, because it uses virCommandRun() which uses virFindFileInPath() to get the real path at runtime. But for tests it won't fly. Alternativelly, if the mdevctl lived under say /bin or any other location than /usr/sbin the expected output would be different.
In virnetdevbandwidthtest.c (which uses TC) I've worked around this by using TC directly to construct expected output. However, I guess we can safely assume that either mdevctl is present at build time so that its location is
Why can we assume it safely? config.log doesn't complain and since we didn't put this in a BuildRequires stanza, even if you do dnf builddep, you won't get the program. Note that the situation with TC is different because (if I overlook gentoo), it is provided by iproute-tc on Fedora and iproute2 on Debian which are installed by default, so TC will be always present. I don't think we should silently assume anything here, I believe configure should complain because otherwise the tests will be failing and the developer would not know why and it would also be confusing since our gitlab pipelines would be green.
Well, is iproute installed even for minimal installation? Because fedora builders certainly don't have it: https://kojipkgs.fedoraproject.org//packages/libvirt/6.1.0/4.fc32/data/logs/... checking for radvd... /usr/sbin/radvd checking for tc... tc (if tc was found then the full path would be printed, like it is for radvd) and we certainly don't want to fail configure if mdevctl wasn't found. What we can do then, is to generate the expected cmd line on the fly - just like virnetdevbandwidthtest does, or skip argv[0] and compare only the rest of the args. Michal

On Thu, Jun 11, 2020 at 04:15:56PM +0200, Michal Privoznik wrote:
On 6/11/20 3:40 PM, Erik Skultety wrote:
On Wed, Jun 10, 2020 at 08:01:47PM +0200, Michal Privoznik wrote:
On 6/9/20 11:43 PM, Jonathon Jongsma wrote:
Test that we run 'mdevctl' with the proper arguments when creating new mediated devices with virNodeDeviceCreateXML().
Signed-off-by: Jonathon Jongsma <jjongsma@redhat.com> --- build-aux/syntax-check.mk | 2 +- tests/Makefile.am | 14 + ...019_36ea_4111_8f0a_8c9a70e21366-start.argv | 1 + ...019_36ea_4111_8f0a_8c9a70e21366-start.json | 1 + ...d39_495e_4243_ad9f_beb3f14c23d9-start.argv | 1 + ...d39_495e_4243_ad9f_beb3f14c23d9-start.json | 1 + ...916_1ca8_49ac_b176_871d16c13076-start.argv | 1 + ...916_1ca8_49ac_b176_871d16c13076-start.json | 1 + tests/nodedevmdevctltest.c | 257 ++++++++++++++++++ ...v_d069d019_36ea_4111_8f0a_8c9a70e21366.xml | 8 + ...v_d2441d39_495e_4243_ad9f_beb3f14c23d9.xml | 10 + ...v_fedc4916_1ca8_49ac_b176_871d16c13076.xml | 9 + 12 files changed, 305 insertions(+), 1 deletion(-) create mode 100644 tests/nodedevmdevctldata/mdev_d069d019_36ea_4111_8f0a_8c9a70e21366-start.argv create mode 100644 tests/nodedevmdevctldata/mdev_d069d019_36ea_4111_8f0a_8c9a70e21366-start.json create mode 100644 tests/nodedevmdevctldata/mdev_d2441d39_495e_4243_ad9f_beb3f14c23d9-start.argv create mode 100644 tests/nodedevmdevctldata/mdev_d2441d39_495e_4243_ad9f_beb3f14c23d9-start.json create mode 100644 tests/nodedevmdevctldata/mdev_fedc4916_1ca8_49ac_b176_871d16c13076-start.argv create mode 100644 tests/nodedevmdevctldata/mdev_fedc4916_1ca8_49ac_b176_871d16c13076-start.json create mode 100644 tests/nodedevmdevctltest.c create mode 100644 tests/nodedevschemadata/mdev_d069d019_36ea_4111_8f0a_8c9a70e21366.xml create mode 100644 tests/nodedevschemadata/mdev_d2441d39_495e_4243_ad9f_beb3f14c23d9.xml create mode 100644 tests/nodedevschemadata/mdev_fedc4916_1ca8_49ac_b176_871d16c13076.xml
diff --git a/build-aux/syntax-check.mk b/build-aux/syntax-check.mk index bf8832a2a5..d47a92b530 100644 --- a/build-aux/syntax-check.mk +++ b/build-aux/syntax-check.mk @@ -2015,7 +2015,7 @@ exclude_file_name_regexp--sc_prohibit_close = \ (\.p[yl]$$|\.spec\.in$$|^docs/|^(src/util/vir(file|event)\.c|src/libvirt-stream\.c|tests/(vir.+mock\.c|commandhelper\.c|qemusecuritymock\.c)|tools/nss/libvirt_nss_(leases|macs)\.c)$$) exclude_file_name_regexp--sc_prohibit_empty_lines_at_EOF = \ - (^tests/(virhostcpu|virpcitest)data/|docs/js/.*\.js|docs/fonts/.*\.woff|\.diff|tests/virconfdata/no-newline\.conf$$) + (^tests/(nodedevmdevctl|virhostcpu|virpcitest)data/|docs/js/.*\.js|docs/fonts/.*\.woff|\.diff|tests/virconfdata/no-newline\.conf$$) exclude_file_name_regexp--sc_prohibit_fork_wrappers = \ (^(src/(util/(vircommand|virdaemon)|lxc/lxc_controller)|tests/testutils)\.c$$) diff --git a/tests/Makefile.am b/tests/Makefile.am index f5766a7790..13cbdbb31e 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -388,6 +388,10 @@ test_programs += storagevolxml2xmltest test_programs += nodedevxml2xmltest +if WITH_NODE_DEVICES +test_programs += nodedevmdevctltest +endif WITH_NODE_DEVICES + test_programs += interfacexml2xmltest test_programs += cputest @@ -970,6 +974,16 @@ nodedevxml2xmltest_SOURCES = \ testutils.c testutils.h nodedevxml2xmltest_LDADD = $(LDADDS) +if WITH_NODE_DEVICES +nodedevmdevctltest_SOURCES = \ + nodedevmdevctltest.c \ + testutils.c testutils.h + +nodedevmdevctltest_LDADD = \ + ../src/libvirt_driver_nodedev_impl.la \ + $(LDADDS) +endif WITH_NODE_DEVICES + interfacexml2xmltest_SOURCES = \ interfacexml2xmltest.c \ testutils.c testutils.h diff --git a/tests/nodedevmdevctldata/mdev_d069d019_36ea_4111_8f0a_8c9a70e21366-start.argv b/tests/nodedevmdevctldata/mdev_d069d019_36ea_4111_8f0a_8c9a70e21366-start.argv new file mode 100644 index 0000000000..dae6dedf7f --- /dev/null +++ b/tests/nodedevmdevctldata/mdev_d069d019_36ea_4111_8f0a_8c9a70e21366-start.argv @@ -0,0 +1 @@ +/usr/sbin/mdevctl start -p 0000:00:02.0 --jsonfile /dev/stdin
This is a problem. If there is no mdevctl found during configure (my case), then MDEVCTL is going to be plain "mdevctl". Which is okay for the nodedev driver, because it uses virCommandRun() which uses virFindFileInPath() to get the real path at runtime. But for tests it won't fly. Alternativelly, if the mdevctl lived under say /bin or any other location than /usr/sbin the expected output would be different.
In virnetdevbandwidthtest.c (which uses TC) I've worked around this by using TC directly to construct expected output. However, I guess we can safely assume that either mdevctl is present at build time so that its location is
Why can we assume it safely? config.log doesn't complain and since we didn't put this in a BuildRequires stanza, even if you do dnf builddep, you won't get the program. Note that the situation with TC is different because (if I overlook gentoo), it is provided by iproute-tc on Fedora and iproute2 on Debian which are installed by default, so TC will be always present. I don't think we should silently assume anything here, I believe configure should complain because otherwise the tests will be failing and the developer would not know why and it would also be confusing since our gitlab pipelines would be green.
Well, is iproute installed even for minimal installation? Because fedora builders certainly don't have it:
https://kojipkgs.fedoraproject.org//packages/libvirt/6.1.0/4.fc32/data/logs/...
checking for radvd... /usr/sbin/radvd checking for tc... tc
Hmm, good point, I had minimal installed, but I also installed the @core package group which apparently has iproute-tc installed.
(if tc was found then the full path would be printed, like it is for radvd)
and we certainly don't want to fail configure if mdevctl wasn't found. What we can do then, is to generate the expected cmd line on the fly - just like
I guess we could go with ^this, but why do we have to "mock" it this way just to let configure pass if this is a build requirement? To me it looks like we're making it unnecessarily complex. Speaking of build requirements, what is your opinion on adding (well goes the same for TC apparently) BuildRequires with mdevctl to the SPEC file? Erik

On 6/11/20 4:32 PM, Erik Skultety wrote:
On Thu, Jun 11, 2020 at 04:15:56PM +0200, Michal Privoznik wrote:
On 6/11/20 3:40 PM, Erik Skultety wrote:
On Wed, Jun 10, 2020 at 08:01:47PM +0200, Michal Privoznik wrote:
On 6/9/20 11:43 PM, Jonathon Jongsma wrote:
Test that we run 'mdevctl' with the proper arguments when creating new mediated devices with virNodeDeviceCreateXML().
Signed-off-by: Jonathon Jongsma <jjongsma@redhat.com> --- build-aux/syntax-check.mk | 2 +- tests/Makefile.am | 14 + ...019_36ea_4111_8f0a_8c9a70e21366-start.argv | 1 + ...019_36ea_4111_8f0a_8c9a70e21366-start.json | 1 + ...d39_495e_4243_ad9f_beb3f14c23d9-start.argv | 1 + ...d39_495e_4243_ad9f_beb3f14c23d9-start.json | 1 + ...916_1ca8_49ac_b176_871d16c13076-start.argv | 1 + ...916_1ca8_49ac_b176_871d16c13076-start.json | 1 + tests/nodedevmdevctltest.c | 257 ++++++++++++++++++ ...v_d069d019_36ea_4111_8f0a_8c9a70e21366.xml | 8 + ...v_d2441d39_495e_4243_ad9f_beb3f14c23d9.xml | 10 + ...v_fedc4916_1ca8_49ac_b176_871d16c13076.xml | 9 + 12 files changed, 305 insertions(+), 1 deletion(-) create mode 100644 tests/nodedevmdevctldata/mdev_d069d019_36ea_4111_8f0a_8c9a70e21366-start.argv create mode 100644 tests/nodedevmdevctldata/mdev_d069d019_36ea_4111_8f0a_8c9a70e21366-start.json create mode 100644 tests/nodedevmdevctldata/mdev_d2441d39_495e_4243_ad9f_beb3f14c23d9-start.argv create mode 100644 tests/nodedevmdevctldata/mdev_d2441d39_495e_4243_ad9f_beb3f14c23d9-start.json create mode 100644 tests/nodedevmdevctldata/mdev_fedc4916_1ca8_49ac_b176_871d16c13076-start.argv create mode 100644 tests/nodedevmdevctldata/mdev_fedc4916_1ca8_49ac_b176_871d16c13076-start.json create mode 100644 tests/nodedevmdevctltest.c create mode 100644 tests/nodedevschemadata/mdev_d069d019_36ea_4111_8f0a_8c9a70e21366.xml create mode 100644 tests/nodedevschemadata/mdev_d2441d39_495e_4243_ad9f_beb3f14c23d9.xml create mode 100644 tests/nodedevschemadata/mdev_fedc4916_1ca8_49ac_b176_871d16c13076.xml
diff --git a/build-aux/syntax-check.mk b/build-aux/syntax-check.mk index bf8832a2a5..d47a92b530 100644 --- a/build-aux/syntax-check.mk +++ b/build-aux/syntax-check.mk @@ -2015,7 +2015,7 @@ exclude_file_name_regexp--sc_prohibit_close = \ (\.p[yl]$$|\.spec\.in$$|^docs/|^(src/util/vir(file|event)\.c|src/libvirt-stream\.c|tests/(vir.+mock\.c|commandhelper\.c|qemusecuritymock\.c)|tools/nss/libvirt_nss_(leases|macs)\.c)$$) exclude_file_name_regexp--sc_prohibit_empty_lines_at_EOF = \ - (^tests/(virhostcpu|virpcitest)data/|docs/js/.*\.js|docs/fonts/.*\.woff|\.diff|tests/virconfdata/no-newline\.conf$$) + (^tests/(nodedevmdevctl|virhostcpu|virpcitest)data/|docs/js/.*\.js|docs/fonts/.*\.woff|\.diff|tests/virconfdata/no-newline\.conf$$) exclude_file_name_regexp--sc_prohibit_fork_wrappers = \ (^(src/(util/(vircommand|virdaemon)|lxc/lxc_controller)|tests/testutils)\.c$$) diff --git a/tests/Makefile.am b/tests/Makefile.am index f5766a7790..13cbdbb31e 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -388,6 +388,10 @@ test_programs += storagevolxml2xmltest test_programs += nodedevxml2xmltest +if WITH_NODE_DEVICES +test_programs += nodedevmdevctltest +endif WITH_NODE_DEVICES + test_programs += interfacexml2xmltest test_programs += cputest @@ -970,6 +974,16 @@ nodedevxml2xmltest_SOURCES = \ testutils.c testutils.h nodedevxml2xmltest_LDADD = $(LDADDS) +if WITH_NODE_DEVICES +nodedevmdevctltest_SOURCES = \ + nodedevmdevctltest.c \ + testutils.c testutils.h + +nodedevmdevctltest_LDADD = \ + ../src/libvirt_driver_nodedev_impl.la \ + $(LDADDS) +endif WITH_NODE_DEVICES + interfacexml2xmltest_SOURCES = \ interfacexml2xmltest.c \ testutils.c testutils.h diff --git a/tests/nodedevmdevctldata/mdev_d069d019_36ea_4111_8f0a_8c9a70e21366-start.argv b/tests/nodedevmdevctldata/mdev_d069d019_36ea_4111_8f0a_8c9a70e21366-start.argv new file mode 100644 index 0000000000..dae6dedf7f --- /dev/null +++ b/tests/nodedevmdevctldata/mdev_d069d019_36ea_4111_8f0a_8c9a70e21366-start.argv @@ -0,0 +1 @@ +/usr/sbin/mdevctl start -p 0000:00:02.0 --jsonfile /dev/stdin
This is a problem. If there is no mdevctl found during configure (my case), then MDEVCTL is going to be plain "mdevctl". Which is okay for the nodedev driver, because it uses virCommandRun() which uses virFindFileInPath() to get the real path at runtime. But for tests it won't fly. Alternativelly, if the mdevctl lived under say /bin or any other location than /usr/sbin the expected output would be different.
In virnetdevbandwidthtest.c (which uses TC) I've worked around this by using TC directly to construct expected output. However, I guess we can safely assume that either mdevctl is present at build time so that its location is
Why can we assume it safely? config.log doesn't complain and since we didn't put this in a BuildRequires stanza, even if you do dnf builddep, you won't get the program. Note that the situation with TC is different because (if I overlook gentoo), it is provided by iproute-tc on Fedora and iproute2 on Debian which are installed by default, so TC will be always present. I don't think we should silently assume anything here, I believe configure should complain because otherwise the tests will be failing and the developer would not know why and it would also be confusing since our gitlab pipelines would be green.
Well, is iproute installed even for minimal installation? Because fedora builders certainly don't have it:
https://kojipkgs.fedoraproject.org//packages/libvirt/6.1.0/4.fc32/data/logs/...
checking for radvd... /usr/sbin/radvd checking for tc... tc
Hmm, good point, I had minimal installed, but I also installed the @core package group which apparently has iproute-tc installed.
(if tc was found then the full path would be printed, like it is for radvd)
and we certainly don't want to fail configure if mdevctl wasn't found. What we can do then, is to generate the expected cmd line on the fly - just like
I guess we could go with ^this, but why do we have to "mock" it this way just to let configure pass if this is a build requirement? To me it looks like we're making it unnecessarily complex.
Configure and spec file are two different things. If the spec file declares something as build dependency it doesn't have to be that way on other distributions. Not to mention other OSes like FreeBSD where I bet is no notion of mediated devices, or when somebody wants to build just the client (e.g. mingw). Requiring mdevctl during configure wouldn't be step in the right direction then.
Speaking of build requirements, what is your opinion on adding (well goes the same for TC apparently) BuildRequires with mdevctl to the SPEC file?
That might be a good step, but if we discard my suggestion to change m4/virt-external-programs.m4 and keep what Jonathon proposed, then I don't see a need for that. Configure will not hardcode any path to mdevctl and virCommandRun() will then use virFindFileInPath() to determine its location at runtime (in virExec()). In the end, I guess we should do what virnetdevbandwidthtest does and use MDEVCTL macro to construct expected output. Then, whatever value the macro has (whatever path mdevctl is found under), the expected output will have the same right one. Michal

On Tue, Jun 09, 2020 at 04:43:47PM -0500, Jonathon Jongsma wrote:
Test that we run 'mdevctl' with the proper arguments when creating new mediated devices with virNodeDeviceCreateXML().
Signed-off-by: Jonathon Jongsma <jjongsma@redhat.com> --- ...
+ + // this function will set a stdin buffer containing the json configuration + // of the device. This json value is captured in the mock wrapper above
nitpick: /* ... */ instead of //
+ cmd = nodeDeviceGetMdevctlStartCommand(def, false, &uuid); + + if (!cmd) + goto cleanup; + + virCommandSetDryRun(&buf, testCommandDryRunCallback, &stdinbuf); + if (virCommandRun(cmd, NULL) < 0) + goto cleanup; + + if (!(actualCmdline = virBufferCurrentContent(&buf))) + goto cleanup; + + if (virTestCompareToFile(actualCmdline, startcmdfile) < 0) + goto cleanup; + + if (virTestCompareToFile(stdinbuf, startjsonfile) < 0) + goto cleanup; + + ret = 0; + + cleanup: + virBufferFreeAndReset(&buf); + virCommandSetDryRun(NULL, NULL, NULL); + virNodeDeviceObjEndAPI(&obj); + return ret; +} + +static int +testMdevctlStartHelper(const void *data) +{ + const struct testInfo *info = data; + + g_autofree char *mdevxml = g_strdup_printf("%s/nodedevschemadata/%s.xml", + abs_srcdir, info->filename); + g_autofree char *cmdlinefile = g_strdup_printf("%s/nodedevmdevctldata/%s-start.argv", + abs_srcdir, info->filename); + g_autofree char *jsonfile = g_strdup_printf("%s/nodedevmdevctldata/%s-start.json", + abs_srcdir, info->filename); + + return testMdevctlStart(info->shouldFail, info->virt_type, + info->create, mdevxml, cmdlinefile, + jsonfile); +} + +static void +nodedevTestDriverFree(virNodeDeviceDriverStatePtr drv) +{ + if (!drv) + return; + + virCondDestroy(&drv->initCond); + virMutexDestroy(&drv->lock); + VIR_FREE(drv->stateDir); + VIR_FREE(drv); +} + +/* Add a fake root 'computer' device */ +static virNodeDeviceDefPtr +fakeRootDevice(void) +{ + virNodeDeviceDefPtr def = NULL; + + if (VIR_ALLOC(def) != 0 || VIR_ALLOC(def->caps) != 0) { + virNodeDeviceDefFree(def); + return NULL; + } + + def->name = g_strdup("computer"); + + return def; +} + +/* Add a fake pci device that can be used as a parent device for mediated + * devices. For our purposes, it only needs to have a name that matches the + * parent of the mdev, and it needs a PCI address + */ +static virNodeDeviceDefPtr +fakeParentDevice(void) +{ + virNodeDeviceDefPtr def = NULL; + virNodeDevCapPCIDevPtr pci_dev; + + if (VIR_ALLOC(def) != 0 || VIR_ALLOC(def->caps) != 0) { + virNodeDeviceDefFree(def); + return NULL; + } + + def->name = g_strdup("pci_0000_00_02_0"); + def->parent = g_strdup("computer"); + + def->caps->data.type = VIR_NODE_DEV_CAP_PCI_DEV; + pci_dev = &def->caps->data.pci_dev; + pci_dev->domain = 0; + pci_dev->bus = 0; + pci_dev->slot = 2; + pci_dev->function = 0; + + return def; +} + +static int +addDevice(virNodeDeviceDefPtr def) +{ + if (!def) + return -1; + + virNodeDeviceObjPtr obj = virNodeDeviceObjListAssignDef(driver->devs, def); + + if (!obj) { + virNodeDeviceDefFree(def); + return -1; + } + + virNodeDeviceObjEndAPI(&obj); + return 0; +} + +static int +nodedevTestDriverAddTestDevices(void) +{ + if (addDevice(fakeRootDevice()) < 0 || + addDevice(fakeParentDevice()) < 0) + return -1; + + return 0; +} + +/* Bare minimum driver init to be able to test nodedev functionality */ +static int +nodedevTestDriverInit(void) +{ + int ret = -1; + char statedir[] = abs_builddir "/nodedevstatedir-XXXXXX"; + if (VIR_ALLOC(driver) < 0) + return -1; + + if (!g_mkdtemp(statedir)) { + fprintf(stderr, "Cannot create fake stateDir"); + goto error; + } + + driver->stateDir = g_strdup(statedir); + driver->lockFD = -1; + if (virMutexInit(&driver->lock) < 0 || + virCondInit(&driver->initCond) < 0) { + VIR_TEST_DEBUG("Unable to initialize test nodedev driver"); + goto error; + } + + if (!(driver->devs = virNodeDeviceObjListNew())) + goto error; + + return 0; + + error: + nodedevTestDriverFree(driver); + return ret; +} + +static int +mymain(void) +{ + int ret = 0; + + if (nodedevTestDriverInit() < 0) + return EXIT_FAILURE; + + if (nodedevTestDriverAddTestDevices() < 0) { + ret = EXIT_FAILURE; + goto done; + } + + // add a mock device to the device list so it can be used as a parent + // reference
..same ^here...
+ +#define DO_TEST_FULL(shouldFail, virt_type, create, filename) \ + do { \ + struct testInfo info = { shouldFail, virt_type, create, filename, }; \ + if (virTestRun("mdevctl start " filename, \ + testMdevctlStartHelper, &info) < 0) \ + ret = -1; \ + } \ + while (0); + +#define DO_TEST(...) \ + DO_TEST_FULL(false, "QEMU", CREATE_DEVICE, __VA_ARGS__) +#define DO_TEST_FAIL(...) \ + DO_TEST_FULL(true, "QEMU", CREATE_DEVICE, __VA_ARGS__) + + // Test mdevctl start commands + DO_TEST("mdev_d069d019_36ea_4111_8f0a_8c9a70e21366"); + DO_TEST("mdev_fedc4916_1ca8_49ac_b176_871d16c13076"); + DO_TEST("mdev_d2441d39_495e_4243_ad9f_beb3f14c23d9"); + + done: + nodedevTestDriverFree(driver); + + return ret == 0 ? EXIT_SUCCESS : EXIT_FAILURE; +} + +VIR_TEST_MAIN(mymain) diff --git a/tests/nodedevschemadata/mdev_d069d019_36ea_4111_8f0a_8c9a70e21366.xml b/tests/nodedevschemadata/mdev_d069d019_36ea_4111_8f0a_8c9a70e21366.xml new file mode 100644 index 0000000000..36d50630e4 --- /dev/null +++ b/tests/nodedevschemadata/mdev_d069d019_36ea_4111_8f0a_8c9a70e21366.xml @@ -0,0 +1,8 @@ +<device> + <name>mdev_d069d019_36ea_4111_8f0a_8c9a70e21366</name> + <parent>pci_0000_00_02_0</parent> + <capability type='mdev'> + <type id='i915-GVTg_V5_8'/> + <iommuGroup number='0'/>
Like I said in the response to one of the earlier patches, I'd like to see iommuGroup gone from the create XML. Reviewed-by: Erik Skultety <eskultet@redhat.com>

Add the ability to destroy mdev node devices via the mdevctl utility. Signed-off-by: Jonathon Jongsma <jjongsma@redhat.com> --- src/node_device/node_device_driver.c | 46 ++++++++++++++++++++++++++++ src/node_device/node_device_driver.h | 3 ++ 2 files changed, 49 insertions(+) diff --git a/src/node_device/node_device_driver.c b/src/node_device/node_device_driver.c index dbc7eb4d1e..c956bb55fc 100644 --- a/src/node_device/node_device_driver.c +++ b/src/node_device/node_device_driver.c @@ -790,6 +790,45 @@ nodeDeviceCreateXML(virConnectPtr conn, } +virCommandPtr +nodeDeviceGetMdevctlStopCommand(const char *uuid, + bool persist) +{ + g_autofree char *mdevctl = virFindFileInPath(MDEVCTL); + const char *subcommand; + + if (!mdevctl) + return NULL; + + if (persist) + subcommand = "undefine"; + else + subcommand = "stop"; + + virCommandPtr cmd = virCommandNewArgList(mdevctl, + subcommand, + "-u", + uuid, + NULL); + + return cmd; +} + +static int +virMdevctlStop(virNodeDeviceDefPtr def) +{ + int status; + g_autoptr(virCommand) cmd = NULL; + + cmd = nodeDeviceGetMdevctlStopCommand(def->caps->data.mdev.uuid, false); + + if (virCommandRun(cmd, &status) < 0 || status != 0) + return -1; + + return 0; +} + + int nodeDeviceDestroy(virNodeDevicePtr device) { @@ -836,6 +875,13 @@ nodeDeviceDestroy(virNodeDevicePtr device) if (virVHBAManageVport(parent_host, wwpn, wwnn, VPORT_DELETE) < 0) goto cleanup; + ret = 0; + } else if (nodeDeviceHasCapability(def, VIR_NODE_DEV_CAP_MDEV)) { + if (virMdevctlStop(def) < 0) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("Unable to stop mediated device")); + goto cleanup; + } ret = 0; } else { virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s", diff --git a/src/node_device/node_device_driver.h b/src/node_device/node_device_driver.h index 576f75375f..794cb5dc1c 100644 --- a/src/node_device/node_device_driver.h +++ b/src/node_device/node_device_driver.h @@ -122,3 +122,6 @@ virCommandPtr nodeDeviceGetMdevctlStartCommand(virNodeDeviceDefPtr def, bool persist, char **uuid_out); +virCommandPtr +nodeDeviceGetMdevctlStopCommand(const char *uuid, + bool persist); -- 2.21.3

On 6/9/20 11:43 PM, Jonathon Jongsma wrote:
Add the ability to destroy mdev node devices via the mdevctl utility.
Signed-off-by: Jonathon Jongsma <jjongsma@redhat.com> --- src/node_device/node_device_driver.c | 46 ++++++++++++++++++++++++++++ src/node_device/node_device_driver.h | 3 ++ 2 files changed, 49 insertions(+)
diff --git a/src/node_device/node_device_driver.c b/src/node_device/node_device_driver.c index dbc7eb4d1e..c956bb55fc 100644 --- a/src/node_device/node_device_driver.c +++ b/src/node_device/node_device_driver.c @@ -790,6 +790,45 @@ nodeDeviceCreateXML(virConnectPtr conn, }
+virCommandPtr +nodeDeviceGetMdevctlStopCommand(const char *uuid, + bool persist) +{ + g_autofree char *mdevctl = virFindFileInPath(MDEVCTL);
Same comment about virFindFileInPath() as in one of previous patches.
+ const char *subcommand; + + if (!mdevctl) + return NULL; + + if (persist) + subcommand = "undefine"; + else + subcommand = "stop"; + + virCommandPtr cmd = virCommandNewArgList(mdevctl, + subcommand, + "-u", + uuid, + NULL);
We don't really like variables being defined in the middle of a block. Fortunately, the variable is not really needed and this can be turned into "return virCommandNewArgList(...)" Squash this in: diff --git i/src/node_device/node_device_driver.c w/src/node_device/node_device_driver.c index 23d18308f7..2c204c7a83 100644 --- i/src/node_device/node_device_driver.c +++ w/src/node_device/node_device_driver.c @@ -790,26 +790,21 @@ virCommandPtr nodeDeviceGetMdevctlStopCommand(const char *uuid, bool persist) { - g_autofree char *mdevctl = virFindFileInPath(MDEVCTL); const char *subcommand; - if (!mdevctl) - return NULL; - if (persist) subcommand = "undefine"; else subcommand = "stop"; - virCommandPtr cmd = virCommandNewArgList(mdevctl, - subcommand, - "-u", - uuid, - NULL); - - return cmd; + return virCommandNewArgList(MDEVCTL, + subcommand, + "-u", + uuid, + NULL); } + static int virMdevctlStop(virNodeDeviceDefPtr def) { Michal

On Tue, Jun 09, 2020 at 04:43:48PM -0500, Jonathon Jongsma wrote:
Add the ability to destroy mdev node devices via the mdevctl utility.
Signed-off-by: Jonathon Jongsma <jjongsma@redhat.com> --- src/node_device/node_device_driver.c | 46 ++++++++++++++++++++++++++++ src/node_device/node_device_driver.h | 3 ++ 2 files changed, 49 insertions(+)
diff --git a/src/node_device/node_device_driver.c b/src/node_device/node_device_driver.c index dbc7eb4d1e..c956bb55fc 100644 --- a/src/node_device/node_device_driver.c +++ b/src/node_device/node_device_driver.c @@ -790,6 +790,45 @@ nodeDeviceCreateXML(virConnectPtr conn, }
+virCommandPtr +nodeDeviceGetMdevctlStopCommand(const char *uuid, + bool persist) +{ + g_autofree char *mdevctl = virFindFileInPath(MDEVCTL); + const char *subcommand; + + if (!mdevctl) + return NULL; + + if (persist) + subcommand = "undefine";
"undefine" is a NOP on active mdevs IIRC, so again the helper name is confusing.
+ else + subcommand = "stop"; + + virCommandPtr cmd = virCommandNewArgList(mdevctl, + subcommand, + "-u", + uuid, + NULL); + + return cmd; +}
Like I mentioned already, we could have a generic translator to the mdevctl subcommands. Regards, Erik

On Thu, 2020-06-11 at 16:00 +0200, Erik Skultety wrote:
On Tue, Jun 09, 2020 at 04:43:48PM -0500, Jonathon Jongsma wrote:
Add the ability to destroy mdev node devices via the mdevctl utility.
Signed-off-by: Jonathon Jongsma <jjongsma@redhat.com> --- src/node_device/node_device_driver.c | 46 ++++++++++++++++++++++++++++ src/node_device/node_device_driver.h | 3 ++ 2 files changed, 49 insertions(+)
diff --git a/src/node_device/node_device_driver.c b/src/node_device/node_device_driver.c index dbc7eb4d1e..c956bb55fc 100644 --- a/src/node_device/node_device_driver.c +++ b/src/node_device/node_device_driver.c @@ -790,6 +790,45 @@ nodeDeviceCreateXML(virConnectPtr conn, }
+virCommandPtr +nodeDeviceGetMdevctlStopCommand(const char *uuid, + bool persist) +{ + g_autofree char *mdevctl = virFindFileInPath(MDEVCTL); + const char *subcommand; + + if (!mdevctl) + return NULL; + + if (persist) + subcommand = "undefine";
"undefine" is a NOP on active mdevs IIRC, so again the helper name is confusing.
Oh, you're right. This part was meant to plan ahead for persistent mediated devices, but since it's not yet used (and since it doesn't have the effect intended, as you point out), it should probably just be removed from this patch series.
+ else + subcommand = "stop"; + + virCommandPtr cmd = virCommandNewArgList(mdevctl, + subcommand, + "-u", + uuid, + NULL); + + return cmd; +}
Like I mentioned already, we could have a generic translator to the mdevctl subcommands.
Regards, Erik

Test that we run 'mdevctl' with the proper arguments when we destroy mediated devices with virNodeDeviceDestroy() Signed-off-by: Jonathon Jongsma <jjongsma@redhat.com> --- tests/nodedevmdevctldata/mdevctl-stop.argv | 1 + tests/nodedevmdevctltest.c | 43 ++++++++++++++++++++++ 2 files changed, 44 insertions(+) create mode 100644 tests/nodedevmdevctldata/mdevctl-stop.argv diff --git a/tests/nodedevmdevctldata/mdevctl-stop.argv b/tests/nodedevmdevctldata/mdevctl-stop.argv new file mode 100644 index 0000000000..25ee7145ce --- /dev/null +++ b/tests/nodedevmdevctldata/mdevctl-stop.argv @@ -0,0 +1 @@ +/usr/sbin/mdevctl stop -u e2451f73-c95b-4124-b900-e008af37c576 diff --git a/tests/nodedevmdevctltest.c b/tests/nodedevmdevctltest.c index 32a22246c2..58ebf976e2 100644 --- a/tests/nodedevmdevctltest.c +++ b/tests/nodedevmdevctltest.c @@ -98,6 +98,42 @@ testMdevctlStartHelper(const void *data) jsonfile); } +static int +testMdevctlStopHelper(const void *data) +{ + const char *uuid = data; + virBuffer buf = VIR_BUFFER_INITIALIZER; + const char *actualCmdline = NULL; + int ret = -1; + g_autoptr(virCommand) cmd = NULL; + + g_autofree char *cmdlinefile = + g_strdup_printf("%s/nodedevmdevctldata/mdevctl-stop.argv", + abs_srcdir); + + cmd = nodeDeviceGetMdevctlStopCommand(uuid, false); + + if (!cmd) + goto cleanup; + + virCommandSetDryRun(&buf, NULL, NULL); + if (virCommandRun(cmd, NULL) < 0) + goto cleanup; + + if (!(actualCmdline = virBufferCurrentContent(&buf))) + goto cleanup; + + if (virTestCompareToFile(actualCmdline, cmdlinefile) < 0) + goto cleanup; + + ret = 0; + + cleanup: + virBufferFreeAndReset(&buf); + virCommandSetDryRun(NULL, NULL, NULL); + return ret; +} + static void nodedevTestDriverFree(virNodeDeviceDriverStatePtr drv) { @@ -248,6 +284,13 @@ mymain(void) DO_TEST("mdev_fedc4916_1ca8_49ac_b176_871d16c13076"); DO_TEST("mdev_d2441d39_495e_4243_ad9f_beb3f14c23d9"); + // Test mdevctl stop command, pass an arbitrary uuid + if (virTestRun("mdevctl stop", testMdevctlStopHelper, + "e2451f73-c95b-4124-b900-e008af37c576") < 0) + + + ret = -1; + done: nodedevTestDriverFree(driver); -- 2.21.3

On Tue, Jun 09, 2020 at 04:43:49PM -0500, Jonathon Jongsma wrote:
Test that we run 'mdevctl' with the proper arguments when we destroy mediated devices with virNodeDeviceDestroy()
Signed-off-by: Jonathon Jongsma <jjongsma@redhat.com> --- tests/nodedevmdevctldata/mdevctl-stop.argv | 1 + tests/nodedevmdevctltest.c | 43 ++++++++++++++++++++++ 2 files changed, 44 insertions(+) create mode 100644 tests/nodedevmdevctldata/mdevctl-stop.argv
diff --git a/tests/nodedevmdevctldata/mdevctl-stop.argv b/tests/nodedevmdevctldata/mdevctl-stop.argv new file mode 100644 index 0000000000..25ee7145ce --- /dev/null +++ b/tests/nodedevmdevctldata/mdevctl-stop.argv @@ -0,0 +1 @@ +/usr/sbin/mdevctl stop -u e2451f73-c95b-4124-b900-e008af37c576 diff --git a/tests/nodedevmdevctltest.c b/tests/nodedevmdevctltest.c index 32a22246c2..58ebf976e2 100644 --- a/tests/nodedevmdevctltest.c +++ b/tests/nodedevmdevctltest.c @@ -98,6 +98,42 @@ testMdevctlStartHelper(const void *data) jsonfile); }
+static int +testMdevctlStopHelper(const void *data)
This is not very symmetric to the StartHelper, maybe we can call this one MdevctlStop directly?
+{ + const char *uuid = data; + virBuffer buf = VIR_BUFFER_INITIALIZER; + const char *actualCmdline = NULL; + int ret = -1; + g_autoptr(virCommand) cmd = NULL; + + g_autofree char *cmdlinefile = + g_strdup_printf("%s/nodedevmdevctldata/mdevctl-stop.argv", + abs_srcdir); + + cmd = nodeDeviceGetMdevctlStopCommand(uuid, false); + + if (!cmd) + goto cleanup; + + virCommandSetDryRun(&buf, NULL, NULL); + if (virCommandRun(cmd, NULL) < 0) + goto cleanup; + + if (!(actualCmdline = virBufferCurrentContent(&buf))) + goto cleanup; + + if (virTestCompareToFile(actualCmdline, cmdlinefile) < 0) + goto cleanup; + + ret = 0; + + cleanup: + virBufferFreeAndReset(&buf); + virCommandSetDryRun(NULL, NULL, NULL); + return ret; +} + static void nodedevTestDriverFree(virNodeDeviceDriverStatePtr drv) { @@ -248,6 +284,13 @@ mymain(void) DO_TEST("mdev_fedc4916_1ca8_49ac_b176_871d16c13076"); DO_TEST("mdev_d2441d39_495e_4243_ad9f_beb3f14c23d9");
+ // Test mdevctl stop command, pass an arbitrary uuid
/* ... */
+ if (virTestRun("mdevctl stop", testMdevctlStopHelper, + "e2451f73-c95b-4124-b900-e008af37c576") < 0)
Can we remain consistent with the usage of macros and rework DO_TEST_FULL so that mdevctl stop could be run through the DO_TEST macro? Erik

As noted by Erik Skultety, we use the same XML schema to report existing devices and to define new devices. However, some schema elements are "read-only". In other words, they are used to report information from the node device driver and cannot be used to define a new device. Note these in the documentation. Signed-off-by: Jonathon Jongsma <jjongsma@redhat.com> --- docs/formatnode.html.in | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/docs/formatnode.html.in b/docs/formatnode.html.in index a46b73254b..651411502c 100644 --- a/docs/formatnode.html.in +++ b/docs/formatnode.html.in @@ -31,11 +31,16 @@ name is just the bus type and address, as in "pci_0000_00_02_1" or "usb_1_5_3", but some devices are able to provide more specific names, such as - "net_eth1_00_27_13_6a_fe_00". + "net_eth1_00_27_13_6a_fe_00". This is a read-only field that is + reported by the device driver. If this element is set when defining a + new device, it will be ignored. + </dd> <dt><code>path</code></dt> <dd> - Fully qualified sysfs path to the device. + Fully qualified sysfs path to the device. This is a read-only field + that is reported by the device driver. If this element is set when + defining a new device, it will be ignored. </dd> <dt><code>parent</code></dt> <dd> -- 2.21.3

On Tue, Jun 09, 2020 at 04:43:50PM -0500, Jonathon Jongsma wrote:
As noted by Erik Skultety, we use the same XML schema to report existing devices and to define new devices. However, some schema elements are "read-only". In other words, they are used to report information from the node device driver and cannot be used to define a new device. Note these in the documentation.
Signed-off-by: Jonathon Jongsma <jjongsma@redhat.com> --- Reviewed-by: Erik Skultety <eskultet@redhat.com>

On 6/9/20 11:43 PM, Jonathon Jongsma wrote:
Apologies for the delay in posting the second version of this series.
This is the first portion of an effort to support persistent mediated devices with libvirt. This first series simply enables creating and destroying non-persistent mediated devices via the virNodeDeviceCreateXML() and virNodeDeviceDestroy() functions. The 'mdevctl' utility[1] provides the backend implementation.
Changes in v2: - review findings fixed - Added Unit testing - passed JSON config via stdin instead of a temporary file (see portability note in patch 5)
[1] https://github.com/mdevctl/mdevctl
Jonathon Jongsma (10): nodedev: factor out nodeDeviceHasCapability() nodedev: add support for mdev attributes nodedev: refactor nodeDeviceFindNewDevice() nodedev: store mdev UUID in mdev caps nodedev: add mdev support to virNodeDeviceCreateXML() nodedev: Build a non-loadable driver lib nodedev: Add testing for 'mdevctl start' nodedev: add mdev support to virNodeDeviceDestroy() nodedev: Add testing for 'mdevctl stop' docs: note node device fields that are read-only
build-aux/syntax-check.mk | 2 +- docs/formatnode.html.in | 16 +- docs/schemas/nodedev.rng | 6 + libvirt.spec.in | 2 + m4/virt-external-programs.m4 | 3 + src/conf/node_device_conf.c | 60 ++- src/conf/node_device_conf.h | 3 + src/conf/virnodedeviceobj.c | 34 ++ src/conf/virnodedeviceobj.h | 3 + src/libvirt_private.syms | 3 + src/node_device/Makefile.inc.am | 23 +- src/node_device/node_device_driver.c | 377 ++++++++++++++++-- src/node_device/node_device_driver.h | 9 + src/node_device/node_device_udev.c | 5 +- src/util/virmdev.c | 12 + src/util/virmdev.h | 11 + tests/Makefile.am | 14 + ...019_36ea_4111_8f0a_8c9a70e21366-start.argv | 1 + ...019_36ea_4111_8f0a_8c9a70e21366-start.json | 1 + ...d39_495e_4243_ad9f_beb3f14c23d9-start.argv | 1 + ...d39_495e_4243_ad9f_beb3f14c23d9-start.json | 1 + ...916_1ca8_49ac_b176_871d16c13076-start.argv | 1 + ...916_1ca8_49ac_b176_871d16c13076-start.json | 1 + tests/nodedevmdevctldata/mdevctl-stop.argv | 1 + tests/nodedevmdevctltest.c | 300 ++++++++++++++ ...v_d069d019_36ea_4111_8f0a_8c9a70e21366.xml | 8 + ...v_d2441d39_495e_4243_ad9f_beb3f14c23d9.xml | 10 + ...v_fedc4916_1ca8_49ac_b176_871d16c13076.xml | 9 + 28 files changed, 862 insertions(+), 55 deletions(-) create mode 100644 tests/nodedevmdevctldata/mdev_d069d019_36ea_4111_8f0a_8c9a70e21366-start.argv create mode 100644 tests/nodedevmdevctldata/mdev_d069d019_36ea_4111_8f0a_8c9a70e21366-start.json create mode 100644 tests/nodedevmdevctldata/mdev_d2441d39_495e_4243_ad9f_beb3f14c23d9-start.argv create mode 100644 tests/nodedevmdevctldata/mdev_d2441d39_495e_4243_ad9f_beb3f14c23d9-start.json create mode 100644 tests/nodedevmdevctldata/mdev_fedc4916_1ca8_49ac_b176_871d16c13076-start.argv create mode 100644 tests/nodedevmdevctldata/mdev_fedc4916_1ca8_49ac_b176_871d16c13076-start.json create mode 100644 tests/nodedevmdevctldata/mdevctl-stop.argv create mode 100644 tests/nodedevmdevctltest.c create mode 100644 tests/nodedevschemadata/mdev_d069d019_36ea_4111_8f0a_8c9a70e21366.xml create mode 100644 tests/nodedevschemadata/mdev_d2441d39_495e_4243_ad9f_beb3f14c23d9.xml create mode 100644 tests/nodedevschemadata/mdev_fedc4916_1ca8_49ac_b176_871d16c13076.xml
Patches look good to me. I've raised couple of small issues (mostly mem leaks), but suggested what needs to be squashed in. I'm keeping it all in my local branch, ready to push if you agree with my comments (no need to send v3 then). However, as in v1, I'd like either Erik or Dan to skim through patches, because I know next to nothing about mdevs. Conditional: Reviewed-by: Michal Privoznik <mprivozn@redhat.com> Michal

On Wed, 2020-06-10 at 20:01 +0200, Michal Privoznik wrote:
On 6/9/20 11:43 PM, Jonathon Jongsma wrote:
Apologies for the delay in posting the second version of this series.
This is the first portion of an effort to support persistent mediated devices with libvirt. This first series simply enables creating and destroying non-persistent mediated devices via the virNodeDeviceCreateXML() and virNodeDeviceDestroy() functions. The 'mdevctl' utility[1] provides the backend implementation.
Changes in v2: - review findings fixed - Added Unit testing - passed JSON config via stdin instead of a temporary file (see portability note in patch 5)
[1] https://github.com/mdevctl/mdevctl
Jonathon Jongsma (10): nodedev: factor out nodeDeviceHasCapability() nodedev: add support for mdev attributes nodedev: refactor nodeDeviceFindNewDevice() nodedev: store mdev UUID in mdev caps nodedev: add mdev support to virNodeDeviceCreateXML() nodedev: Build a non-loadable driver lib nodedev: Add testing for 'mdevctl start' nodedev: add mdev support to virNodeDeviceDestroy() nodedev: Add testing for 'mdevctl stop' docs: note node device fields that are read-only
build-aux/syntax-check.mk | 2 +- docs/formatnode.html.in | 16 +- docs/schemas/nodedev.rng | 6 + libvirt.spec.in | 2 + m4/virt-external-programs.m4 | 3 + src/conf/node_device_conf.c | 60 ++- src/conf/node_device_conf.h | 3 + src/conf/virnodedeviceobj.c | 34 ++ src/conf/virnodedeviceobj.h | 3 + src/libvirt_private.syms | 3 + src/node_device/Makefile.inc.am | 23 +- src/node_device/node_device_driver.c | 377 ++++++++++++++++-- src/node_device/node_device_driver.h | 9 + src/node_device/node_device_udev.c | 5 +- src/util/virmdev.c | 12 + src/util/virmdev.h | 11 + tests/Makefile.am | 14 + ...019_36ea_4111_8f0a_8c9a70e21366-start.argv | 1 + ...019_36ea_4111_8f0a_8c9a70e21366-start.json | 1 + ...d39_495e_4243_ad9f_beb3f14c23d9-start.argv | 1 + ...d39_495e_4243_ad9f_beb3f14c23d9-start.json | 1 + ...916_1ca8_49ac_b176_871d16c13076-start.argv | 1 + ...916_1ca8_49ac_b176_871d16c13076-start.json | 1 + tests/nodedevmdevctldata/mdevctl-stop.argv | 1 + tests/nodedevmdevctltest.c | 300 ++++++++++++++ ...v_d069d019_36ea_4111_8f0a_8c9a70e21366.xml | 8 + ...v_d2441d39_495e_4243_ad9f_beb3f14c23d9.xml | 10 + ...v_fedc4916_1ca8_49ac_b176_871d16c13076.xml | 9 + 28 files changed, 862 insertions(+), 55 deletions(-) create mode 100644 tests/nodedevmdevctldata/mdev_d069d019_36ea_4111_8f0a_8c9a70e21366- start.argv create mode 100644 tests/nodedevmdevctldata/mdev_d069d019_36ea_4111_8f0a_8c9a70e21366- start.json create mode 100644 tests/nodedevmdevctldata/mdev_d2441d39_495e_4243_ad9f_beb3f14c23d9- start.argv create mode 100644 tests/nodedevmdevctldata/mdev_d2441d39_495e_4243_ad9f_beb3f14c23d9- start.json create mode 100644 tests/nodedevmdevctldata/mdev_fedc4916_1ca8_49ac_b176_871d16c13076- start.argv create mode 100644 tests/nodedevmdevctldata/mdev_fedc4916_1ca8_49ac_b176_871d16c13076- start.json create mode 100644 tests/nodedevmdevctldata/mdevctl-stop.argv create mode 100644 tests/nodedevmdevctltest.c create mode 100644 tests/nodedevschemadata/mdev_d069d019_36ea_4111_8f0a_8c9a70e21366.x ml create mode 100644 tests/nodedevschemadata/mdev_d2441d39_495e_4243_ad9f_beb3f14c23d9.x ml create mode 100644 tests/nodedevschemadata/mdev_fedc4916_1ca8_49ac_b176_871d16c13076.x ml
Patches look good to me. I've raised couple of small issues (mostly mem leaks), but suggested what needs to be squashed in. I'm keeping it all in my local branch, ready to push if you agree with my comments (no need to send v3 then).
I have no objections to your squashed patches. Thanks for that.
However, as in v1, I'd like either Erik or Dan to skim through patches, because I know next to nothing about mdevs.
Conditional:
Reviewed-by: Michal Privoznik <mprivozn@redhat.com>
Michal

On 6/10/20 10:09 PM, Jonathon Jongsma wrote:
On Wed, 2020-06-10 at 20:01 +0200, Michal Privoznik wrote:
Patches look good to me. I've raised couple of small issues (mostly mem leaks), but suggested what needs to be squashed in. I'm keeping it all in my local branch, ready to push if you agree with my comments (no need to send v3 then).
I have no objections to your squashed patches. Thanks for that.
Based on the discussion in 07/10, we will need slightly different approach to tests. Unfortunately, I have to retract my offer, but I will review v3. Fortunately, I have posted all squash ins that I found. Michal
participants (3)
-
Erik Skultety
-
Jonathon Jongsma
-
Michal Privoznik