[PATCH v2 00/11] nodedev state and update

The series add a dual state to the mdev node devices as these objects can be active and defined at the same time. These two states can become different. To be able to also introspect the persisted and transient nodedevs filtering is added. To be able to also dump the XML of an inactive state while the node device is active a new option is added. The last three patches add the capability to update a mdev node device. This can be done on the persisted configuration, on the active configuration or on both. To support this v1.3.0 of mdevctl is required. Changes since v1: * replaced spec file requirement for v1.3.0 of mdevctl by a dynamic support check and an unsupported message if not available. * removed persisted precheck in virsh * renamed persisted and persist into persistent * addressed all other review comments made on v1 * added NEWS Boris Fiuczynski (11): virmdev: prepare type and attributes for dual state node_device: refactor mdev attributes handling node_device: remove unnecessary checks in virNodeDeviceDefFormat nodedev: add an active config to mdev tools: add option inactive to nodedev-dumpxml nodedev: add persisted and transient filter on list tools: add switches persisted and transient to nodedev-list virsh: doc fix on nodedev-list api: add virNodeDeviceUpdate() nodedev: Implement virNodeDeviceUpdateXML virsh: add nodedev-update NEWS.rst | 7 + docs/manpages/virsh.rst | 36 +- include/libvirt/libvirt-nodedev.h | 31 ++ libvirt.spec.in | 1 + src/access/viraccessperm.c | 1 + src/access/viraccessperm.h | 6 + src/conf/node_device_conf.c | 76 +-- src/conf/node_device_conf.h | 14 +- src/conf/virnodedeviceobj.c | 50 ++ src/conf/virnodedeviceobj.h | 3 + src/driver-nodedev.h | 6 + src/libvirt-nodedev.c | 47 +- src/libvirt_private.syms | 1 + src/libvirt_public.syms | 5 + src/node_device/node_device_driver.c | 459 +++++++++++++++--- src/node_device/node_device_driver.h | 17 +- src/node_device/node_device_udev.c | 5 +- src/remote/remote_driver.c | 1 + src/remote/remote_protocol.x | 17 +- src/remote_protocol-structs | 6 + src/test/test_driver.c | 6 +- src/util/virmdev.h | 6 + ...60c_c60c_c60c_c60c_c60cc60cc60c_update.xml | 16 + tests/nodedevmdevctldata/mdevctl-modify.argv | 25 + tests/nodedevmdevctldata/mdevctl-modify.json | 1 + tests/nodedevmdevctltest.c | 94 +++- ...v_c60cc60c_c60c_c60c_c60c_c60cc60cc60c.xml | 14 + ...d_b7f0_4fea_b468_f1da537d301b_inactive.xml | 1 + ...v_c60cc60c_c60c_c60c_c60c_c60cc60cc60c.xml | 10 + ...c_c60c_c60c_c60c_c60cc60cc60c_inactive.xml | 9 + ...9_36ea_4111_8f0a_8c9a70e21366_inactive.xml | 1 + ...9_495e_4243_ad9f_beb3f14c23d9_inactive.xml | 1 + ...4_f554_4dc1_809d_b2a01e8e48ad_inactive.xml | 8 + ...6_1ca8_49ac_b176_871d16c13076_inactive.xml | 1 + tests/nodedevxml2xmltest.c | 59 ++- tools/virsh-nodedev.c | 132 ++++- 36 files changed, 1030 insertions(+), 143 deletions(-) create mode 100644 tests/nodedevmdevctldata/mdev_c60cc60c_c60c_c60c_c60c_c60cc60cc60c_update.xml create mode 100644 tests/nodedevmdevctldata/mdevctl-modify.argv create mode 100644 tests/nodedevmdevctldata/mdevctl-modify.json create mode 100644 tests/nodedevschemadata/mdev_c60cc60c_c60c_c60c_c60c_c60cc60cc60c.xml create mode 120000 tests/nodedevxml2xmlout/mdev_3627463d_b7f0_4fea_b468_f1da537d301b_inactive.xml create mode 100644 tests/nodedevxml2xmlout/mdev_c60cc60c_c60c_c60c_c60c_c60cc60cc60c.xml create mode 100644 tests/nodedevxml2xmlout/mdev_c60cc60c_c60c_c60c_c60c_c60cc60cc60c_inactive.xml create mode 120000 tests/nodedevxml2xmlout/mdev_d069d019_36ea_4111_8f0a_8c9a70e21366_inactive.xml create mode 120000 tests/nodedevxml2xmlout/mdev_d2441d39_495e_4243_ad9f_beb3f14c23d9_inactive.xml create mode 100644 tests/nodedevxml2xmlout/mdev_ee0b88c4_f554_4dc1_809d_b2a01e8e48ad_inactive.xml create mode 120000 tests/nodedevxml2xmlout/mdev_fedc4916_1ca8_49ac_b176_871d16c13076_inactive.xml -- 2.42.0

Create a new structure holding type and attributes as these are modifable in a persisted mdev configuration and run out of sync with the active mdev configuration. Signed-off-by: Boris Fiuczynski <fiuczy@linux.ibm.com> --- src/conf/node_device_conf.c | 18 +++---- src/conf/node_device_conf.h | 4 +- src/node_device/node_device_driver.c | 70 ++++++++++++++-------------- src/node_device/node_device_udev.c | 2 +- src/util/virmdev.h | 6 +++ 5 files changed, 52 insertions(+), 48 deletions(-) diff --git a/src/conf/node_device_conf.c b/src/conf/node_device_conf.c index b8c91d6ecd..59d69ff985 100644 --- a/src/conf/node_device_conf.c +++ b/src/conf/node_device_conf.c @@ -593,15 +593,15 @@ virNodeDeviceCapMdevDefFormat(virBuffer *buf, { size_t i; - virBufferEscapeString(buf, "<type id='%s'/>\n", data->mdev.type); + virBufferEscapeString(buf, "<type id='%s'/>\n", data->mdev.dev_config.type); virBufferEscapeString(buf, "<uuid>%s</uuid>\n", data->mdev.uuid); virBufferEscapeString(buf, "<parent_addr>%s</parent_addr>\n", data->mdev.parent_addr); virBufferAsprintf(buf, "<iommuGroup number='%u'/>\n", data->mdev.iommuGroupNumber); - for (i = 0; i < data->mdev.nattributes; i++) { - virMediatedDeviceAttr *attr = data->mdev.attributes[i]; + for (i = 0; i < data->mdev.dev_config.nattributes; i++) { + virMediatedDeviceAttr *attr = data->mdev.dev_config.attributes[i]; virBufferAsprintf(buf, "<attr name='%s' value='%s'/>\n", attr->name, attr->value); } @@ -2183,7 +2183,7 @@ virNodeDevCapMdevAttributeParseXML(xmlXPathContextPtr ctxt, return -1; } - VIR_APPEND_ELEMENT(mdev->attributes, mdev->nattributes, attr); + VIR_APPEND_ELEMENT(mdev->dev_config.attributes, mdev->dev_config.nattributes, attr); return 0; } @@ -2202,7 +2202,7 @@ virNodeDevCapMdevParseXML(xmlXPathContextPtr ctxt, ctxt->node = node; - if (!(mdev->type = virXPathString("string(./type[1]/@id)", ctxt))) { + if (!(mdev->dev_config.type = virXPathString("string(./type[1]/@id)", ctxt))) { virReportError(VIR_ERR_CONFIG_UNSUPPORTED, _("missing type id attribute for '%1$s'"), def->name); return -1; @@ -2577,11 +2577,11 @@ virNodeDevCapsDefFree(virNodeDevCapsDef *caps) g_free(data->sg.path); break; case VIR_NODE_DEV_CAP_MDEV: - g_free(data->mdev.type); + g_free(data->mdev.dev_config.type); g_free(data->mdev.uuid); - for (i = 0; i < data->mdev.nattributes; i++) - virMediatedDeviceAttrFree(data->mdev.attributes[i]); - g_free(data->mdev.attributes); + for (i = 0; i < data->mdev.dev_config.nattributes; i++) + virMediatedDeviceAttrFree(data->mdev.dev_config.attributes[i]); + g_free(data->mdev.dev_config.attributes); g_free(data->mdev.parent_addr); break; case VIR_NODE_DEV_CAP_CSS_DEV: diff --git a/src/conf/node_device_conf.h b/src/conf/node_device_conf.h index 2b2c8f797e..f100496272 100644 --- a/src/conf/node_device_conf.h +++ b/src/conf/node_device_conf.h @@ -151,11 +151,9 @@ struct _virNodeDevCapSystem { typedef struct _virNodeDevCapMdev virNodeDevCapMdev; struct _virNodeDevCapMdev { - char *type; unsigned int iommuGroupNumber; char *uuid; - virMediatedDeviceAttr **attributes; - size_t nattributes; + virMediatedDeviceConfig dev_config; char *parent_addr; bool autostart; }; diff --git a/src/node_device/node_device_driver.c b/src/node_device/node_device_driver.c index a59cd0875d..1ee59d710b 100644 --- a/src/node_device/node_device_driver.c +++ b/src/node_device/node_device_driver.c @@ -609,17 +609,17 @@ nodeDeviceDefToMdevctlConfig(virNodeDeviceDef *def, char **buf) g_autoptr(virJSONValue) json = virJSONValueNewObject(); const char *startval = mdev->autostart ? "auto" : "manual"; - if (virJSONValueObjectAppendString(json, "mdev_type", mdev->type) < 0) + if (virJSONValueObjectAppendString(json, "mdev_type", mdev->dev_config.type) < 0) return -1; if (virJSONValueObjectAppendString(json, "start", startval) < 0) return -1; - if (mdev->attributes) { + if (mdev->dev_config.attributes) { g_autoptr(virJSONValue) attributes = virJSONValueNewArray(); - for (i = 0; i < mdev->nattributes; i++) { - virMediatedDeviceAttr *attr = mdev->attributes[i]; + for (i = 0; i < mdev->dev_config.nattributes; i++) { + virMediatedDeviceAttr *attr = mdev->dev_config.attributes[i]; g_autoptr(virJSONValue) jsonattr = virJSONValueNewObject(); if (virJSONValueObjectAppendString(jsonattr, attr->name, attr->value) < 0) @@ -1129,7 +1129,7 @@ nodeDeviceParseMdevctlChildDevice(const char *parent, mdev = &child->caps->data.mdev; mdev->uuid = g_strdup(uuid); mdev->parent_addr = g_strdup(parent); - mdev->type = + mdev->dev_config.type = g_strdup(virJSONValueObjectGetString(props, "mdev_type")); start = virJSONValueObjectGetString(props, "start"); mdev->autostart = STREQ_NULLABLE(start, "auto"); @@ -1140,8 +1140,8 @@ nodeDeviceParseMdevctlChildDevice(const char *parent, size_t i; int nattrs = virJSONValueArraySize(attrs); - mdev->attributes = g_new0(virMediatedDeviceAttr*, nattrs); - mdev->nattributes = nattrs; + mdev->dev_config.attributes = g_new0(virMediatedDeviceAttr*, nattrs); + mdev->dev_config.nattributes = nattrs; for (i = 0; i < nattrs; i++) { virJSONValue *attr = virJSONValueArrayGet(attrs, i); @@ -1156,7 +1156,7 @@ nodeDeviceParseMdevctlChildDevice(const char *parent, attribute->name = g_strdup(virJSONValueObjectGetKey(attr, 0)); value = virJSONValueObjectGetValue(attr, 0); attribute->value = g_strdup(virJSONValueGetString(value)); - mdev->attributes[i] = attribute; + mdev->dev_config.attributes[i] = attribute; } } mdevGenerateDeviceName(child); @@ -1762,39 +1762,39 @@ nodeDeviceUpdateMediatedDevices(void) /* returns true if any attributes were copied, else returns false */ static bool -virMediatedDeviceAttrsCopy(virNodeDevCapMdev *dst, - virNodeDevCapMdev *src) +virMediatedDeviceAttrsCopy(virMediatedDeviceConfig *dst_config, + virMediatedDeviceConfig *src_config) { bool ret = false; size_t i; - if (src->nattributes != dst->nattributes) { + if (src_config->nattributes != dst_config->nattributes) { ret = true; - for (i = 0; i < dst->nattributes; i++) - virMediatedDeviceAttrFree(dst->attributes[i]); - g_free(dst->attributes); - - dst->nattributes = src->nattributes; - dst->attributes = g_new0(virMediatedDeviceAttr*, - src->nattributes); - for (i = 0; i < dst->nattributes; i++) - dst->attributes[i] = virMediatedDeviceAttrNew(); + for (i = 0; i < dst_config->nattributes; i++) + virMediatedDeviceAttrFree(dst_config->attributes[i]); + g_free(dst_config->attributes); + + dst_config->nattributes = src_config->nattributes; + dst_config->attributes = g_new0(virMediatedDeviceAttr*, + src_config->nattributes); + for (i = 0; i < dst_config->nattributes; i++) + dst_config->attributes[i] = virMediatedDeviceAttrNew(); } - for (i = 0; i < src->nattributes; i++) { - if (STRNEQ_NULLABLE(src->attributes[i]->name, - dst->attributes[i]->name)) { + for (i = 0; i < src_config->nattributes; i++) { + if (STRNEQ_NULLABLE(src_config->attributes[i]->name, + dst_config->attributes[i]->name)) { ret = true; - g_free(dst->attributes[i]->name); - dst->attributes[i]->name = - g_strdup(src->attributes[i]->name); + g_free(dst_config->attributes[i]->name); + dst_config->attributes[i]->name = + g_strdup(src_config->attributes[i]->name); } - if (STRNEQ_NULLABLE(src->attributes[i]->value, - dst->attributes[i]->value)) { + if (STRNEQ_NULLABLE(src_config->attributes[i]->value, + dst_config->attributes[i]->value)) { ret = true; - g_free(dst->attributes[i]->value); - dst->attributes[i]->value = - g_strdup(src->attributes[i]->value); + g_free(dst_config->attributes[i]->value); + dst_config->attributes[i]->value = + g_strdup(src_config->attributes[i]->value); } } @@ -1813,10 +1813,10 @@ nodeDeviceDefCopyFromMdevctl(virNodeDeviceDef *dst, virNodeDevCapMdev *srcmdev = &src->caps->data.mdev; virNodeDevCapMdev *dstmdev = &dst->caps->data.mdev; - if (STRNEQ_NULLABLE(dstmdev->type, srcmdev->type)) { + if (STRNEQ_NULLABLE(dstmdev->dev_config.type, srcmdev->dev_config.type)) { ret = true; - g_free(dstmdev->type); - dstmdev->type = g_strdup(srcmdev->type); + g_free(dstmdev->dev_config.type); + dstmdev->dev_config.type = g_strdup(srcmdev->dev_config.type); } if (STRNEQ_NULLABLE(dstmdev->uuid, srcmdev->uuid)) { @@ -1825,7 +1825,7 @@ nodeDeviceDefCopyFromMdevctl(virNodeDeviceDef *dst, dstmdev->uuid = g_strdup(srcmdev->uuid); } - if (virMediatedDeviceAttrsCopy(dstmdev, srcmdev)) + if (virMediatedDeviceAttrsCopy(&dstmdev->dev_config, &srcmdev->dev_config)) ret = true; if (dstmdev->autostart != srcmdev->autostart) { diff --git a/src/node_device/node_device_udev.c b/src/node_device/node_device_udev.c index faddbef929..254e802c50 100644 --- a/src/node_device/node_device_udev.c +++ b/src/node_device/node_device_udev.c @@ -1069,7 +1069,7 @@ udevProcessMediatedDevice(struct udev_device *dev, return -1; } - data->type = g_path_get_basename(canonicalpath); + data->dev_config.type = g_path_get_basename(canonicalpath); data->uuid = g_strdup(udev_device_get_sysname(dev)); if ((iommugrp = virMediatedDeviceGetIOMMUGroupNum(data->uuid)) < 0) diff --git a/src/util/virmdev.h b/src/util/virmdev.h index bc8306d0e1..853041ac06 100644 --- a/src/util/virmdev.h +++ b/src/util/virmdev.h @@ -47,6 +47,12 @@ G_DEFINE_AUTOPTR_CLEANUP_FUNC(virMediatedDeviceAttr, virMediatedDeviceAttrFree); G_DEFINE_AUTOPTR_CLEANUP_FUNC(virMediatedDeviceList, virObjectUnref); +typedef struct _virMediatedDeviceConfig virMediatedDeviceConfig; +struct _virMediatedDeviceConfig { + char *type; + virMediatedDeviceAttr **attributes; + size_t nattributes; +}; typedef struct _virMediatedDeviceType virMediatedDeviceType; struct _virMediatedDeviceType { -- 2.42.0

Refactor attribute handling code into methods for easier reuse. Signed-off-by: Boris Fiuczynski <fiuczy@linux.ibm.com> Reviewed-by: Jonathon Jongsma <jjongsma@redhat.com> --- src/conf/node_device_conf.c | 27 ++++--- src/node_device/node_device_driver.c | 104 ++++++++++++++++----------- 2 files changed, 80 insertions(+), 51 deletions(-) diff --git a/src/conf/node_device_conf.c b/src/conf/node_device_conf.c index 59d69ff985..a8554102fc 100644 --- a/src/conf/node_device_conf.c +++ b/src/conf/node_device_conf.c @@ -588,11 +588,22 @@ virNodeDeviceCapStorageDefFormat(virBuffer *buf, } static void -virNodeDeviceCapMdevDefFormat(virBuffer *buf, - const virNodeDevCapData *data) +virNodeDeviceCapMdevAttrFormat(virBuffer *buf, + const virMediatedDeviceConfig *config) { size_t i; + for (i = 0; i < config->nattributes; i++) { + virMediatedDeviceAttr *attr = config->attributes[i]; + virBufferAsprintf(buf, "<attr name='%s' value='%s'/>\n", + attr->name, attr->value); + } +} + +static void +virNodeDeviceCapMdevDefFormat(virBuffer *buf, + const virNodeDevCapData *data) +{ virBufferEscapeString(buf, "<type id='%s'/>\n", data->mdev.dev_config.type); virBufferEscapeString(buf, "<uuid>%s</uuid>\n", data->mdev.uuid); virBufferEscapeString(buf, "<parent_addr>%s</parent_addr>\n", @@ -600,11 +611,7 @@ virNodeDeviceCapMdevDefFormat(virBuffer *buf, virBufferAsprintf(buf, "<iommuGroup number='%u'/>\n", data->mdev.iommuGroupNumber); - for (i = 0; i < data->mdev.dev_config.nattributes; i++) { - virMediatedDeviceAttr *attr = data->mdev.dev_config.attributes[i]; - virBufferAsprintf(buf, "<attr name='%s' value='%s'/>\n", - attr->name, attr->value); - } + virNodeDeviceCapMdevAttrFormat(buf, &data->mdev.dev_config); } static void @@ -2169,7 +2176,7 @@ virNodeDevCapSystemParseXML(xmlXPathContextPtr ctxt, static int virNodeDevCapMdevAttributeParseXML(xmlXPathContextPtr ctxt, xmlNodePtr node, - virNodeDevCapMdev *mdev) + virMediatedDeviceConfig *config) { VIR_XPATH_NODE_AUTORESTORE(ctxt) g_autoptr(virMediatedDeviceAttr) attr = virMediatedDeviceAttrNew(); @@ -2183,7 +2190,7 @@ virNodeDevCapMdevAttributeParseXML(xmlXPathContextPtr ctxt, return -1; } - VIR_APPEND_ELEMENT(mdev->dev_config.attributes, mdev->dev_config.nattributes, attr); + VIR_APPEND_ELEMENT(config->attributes, config->nattributes, attr); return 0; } @@ -2234,7 +2241,7 @@ virNodeDevCapMdevParseXML(xmlXPathContextPtr ctxt, return -1; for (i = 0; i < nattrs; i++) - virNodeDevCapMdevAttributeParseXML(ctxt, attrs[i], mdev); + virNodeDevCapMdevAttributeParseXML(ctxt, attrs[i], &mdev->dev_config); return 0; } diff --git a/src/node_device/node_device_driver.c b/src/node_device/node_device_driver.c index 1ee59d710b..7118f833f6 100644 --- a/src/node_device/node_device_driver.c +++ b/src/node_device/node_device_driver.c @@ -599,27 +599,16 @@ nodeDeviceHasCapability(virNodeDeviceDef *def, virNodeDevCapType type) } -/* format a json string that provides configuration information about this mdev - * to the mdevctl utility */ static int -nodeDeviceDefToMdevctlConfig(virNodeDeviceDef *def, char **buf) +nodeDeviceAttributesToJSON(virJSONValue *json, + virMediatedDeviceConfig *config) { size_t i; - virNodeDevCapMdev *mdev = &def->caps->data.mdev; - g_autoptr(virJSONValue) json = virJSONValueNewObject(); - const char *startval = mdev->autostart ? "auto" : "manual"; - - if (virJSONValueObjectAppendString(json, "mdev_type", mdev->dev_config.type) < 0) - return -1; - - if (virJSONValueObjectAppendString(json, "start", startval) < 0) - return -1; - - if (mdev->dev_config.attributes) { + if (config->attributes) { g_autoptr(virJSONValue) attributes = virJSONValueNewArray(); - for (i = 0; i < mdev->dev_config.nattributes; i++) { - virMediatedDeviceAttr *attr = mdev->dev_config.attributes[i]; + for (i = 0; i < config->nattributes; i++) { + virMediatedDeviceAttr *attr = config->attributes[i]; g_autoptr(virJSONValue) jsonattr = virJSONValueNewObject(); if (virJSONValueObjectAppendString(jsonattr, attr->name, attr->value) < 0) @@ -633,6 +622,28 @@ nodeDeviceDefToMdevctlConfig(virNodeDeviceDef *def, char **buf) return -1; } + return 0; +} + + +/* format a json string that provides configuration information about this mdev + * to the mdevctl utility */ +static int +nodeDeviceDefToMdevctlConfig(virNodeDeviceDef *def, char **buf) +{ + virNodeDevCapMdev *mdev = &def->caps->data.mdev; + g_autoptr(virJSONValue) json = virJSONValueNewObject(); + const char *startval = mdev->autostart ? "auto" : "manual"; + + if (virJSONValueObjectAppendString(json, "mdev_type", mdev->dev_config.type) < 0) + return -1; + + if (virJSONValueObjectAppendString(json, "start", startval) < 0) + return -1; + + if (nodeDeviceAttributesToJSON(json, &mdev->dev_config) < 0) + return -1; + *buf = virJSONValueToString(json, false); if (!*buf) return -1; @@ -1092,6 +1103,39 @@ matchDeviceAddress(virNodeDeviceObj *obj, } +static int +nodeDeviceParseMdevctlAttributes(virMediatedDeviceConfig *config, + virJSONValue *attrs) +{ + size_t i; + + if (attrs && virJSONValueIsArray(attrs)) { + int nattrs = virJSONValueArraySize(attrs); + + config->attributes = g_new0(virMediatedDeviceAttr*, nattrs); + config->nattributes = nattrs; + + for (i = 0; i < nattrs; i++) { + virJSONValue *attr = virJSONValueArrayGet(attrs, i); + virMediatedDeviceAttr *attribute; + virJSONValue *value; + + if (!virJSONValueIsObject(attr) || + virJSONValueObjectKeysNumber(attr) != 1) + return -1; + + attribute = g_new0(virMediatedDeviceAttr, 1); + attribute->name = g_strdup(virJSONValueObjectGetKey(attr, 0)); + value = virJSONValueObjectGetValue(attr, 0); + attribute->value = g_strdup(virJSONValueGetString(value)); + config->attributes[i] = attribute; + } + } + + return 0; +} + + static virNodeDeviceDef* nodeDeviceParseMdevctlChildDevice(const char *parent, virJSONValue *json) @@ -1099,7 +1143,6 @@ nodeDeviceParseMdevctlChildDevice(const char *parent, virNodeDevCapMdev *mdev; const char *uuid; virJSONValue *props; - virJSONValue *attrs; g_autoptr(virNodeDeviceDef) child = g_new0(virNodeDeviceDef, 1); virNodeDeviceObj *parent_obj; const char *start = NULL; @@ -1134,31 +1177,10 @@ nodeDeviceParseMdevctlChildDevice(const char *parent, start = virJSONValueObjectGetString(props, "start"); mdev->autostart = STREQ_NULLABLE(start, "auto"); - attrs = virJSONValueObjectGet(props, "attrs"); - - if (attrs && virJSONValueIsArray(attrs)) { - size_t i; - int nattrs = virJSONValueArraySize(attrs); - - mdev->dev_config.attributes = g_new0(virMediatedDeviceAttr*, nattrs); - mdev->dev_config.nattributes = nattrs; - - for (i = 0; i < nattrs; i++) { - virJSONValue *attr = virJSONValueArrayGet(attrs, i); - virMediatedDeviceAttr *attribute; - virJSONValue *value; - - if (!virJSONValueIsObject(attr) || - virJSONValueObjectKeysNumber(attr) != 1) - return NULL; + if (nodeDeviceParseMdevctlAttributes(&mdev->dev_config, + virJSONValueObjectGet(props, "attrs")) < 0) + return NULL; - attribute = g_new0(virMediatedDeviceAttr, 1); - attribute->name = g_strdup(virJSONValueObjectGetKey(attr, 0)); - value = virJSONValueObjectGetValue(attr, 0); - attribute->value = g_strdup(virJSONValueGetString(value)); - mdev->dev_config.attributes[i] = attribute; - } - } mdevGenerateDeviceName(child); return g_steal_pointer(&child); -- 2.42.0

virBufferEscapeString already contains the null check. Signed-off-by: Boris Fiuczynski <fiuczy@linux.ibm.com> --- src/conf/node_device_conf.c | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/conf/node_device_conf.c b/src/conf/node_device_conf.c index a8554102fc..febc58afd1 100644 --- a/src/conf/node_device_conf.c +++ b/src/conf/node_device_conf.c @@ -672,16 +672,14 @@ virNodeDeviceDefFormat(const virNodeDeviceDef *def) virBufferAdjustIndent(&buf, 2); virBufferEscapeString(&buf, "<name>%s</name>\n", def->name); virBufferEscapeString(&buf, "<path>%s</path>\n", def->sysfs_path); - if (def->devnode) - virBufferEscapeString(&buf, "<devnode type='dev'>%s</devnode>\n", - def->devnode); + virBufferEscapeString(&buf, "<devnode type='dev'>%s</devnode>\n", + def->devnode); if (def->devlinks) { for (i = 0; def->devlinks[i]; i++) virBufferEscapeString(&buf, "<devnode type='link'>%s</devnode>\n", def->devlinks[i]); } - if (def->parent) - virBufferEscapeString(&buf, "<parent>%s</parent>\n", def->parent); + virBufferEscapeString(&buf, "<parent>%s</parent>\n", def->parent); if (def->driver) { virBufferAddLit(&buf, "<driver>\n"); virBufferAdjustIndent(&buf, 2); -- 2.42.0

The configuration of a defined mdev can be modified after the mdev is started. The defined configuration and the active configuration can therefore run out of sync. Handle this by storing the modifiable data which is the mdev type and attributes in two separate active and defined configurations. mdevctl supports with callout scripts to do an attribute retrieval of started mdevs which is already implemented in libvirt. Signed-off-by: Boris Fiuczynski <fiuczy@linux.ibm.com> Reviewed-by: Jonathon Jongsma <jjongsma@redhat.com> --- include/libvirt/libvirt-nodedev.h | 11 ++++ src/conf/node_device_conf.c | 53 ++++++++++------ src/conf/node_device_conf.h | 5 +- src/libvirt-nodedev.c | 2 +- src/node_device/node_device_driver.c | 61 +++++++++++++------ src/node_device/node_device_driver.h | 6 +- src/node_device/node_device_udev.c | 4 +- src/test/test_driver.c | 6 +- tests/nodedevmdevctltest.c | 4 +- ...v_c60cc60c_c60c_c60c_c60c_c60cc60cc60c.xml | 14 +++++ ...d_b7f0_4fea_b468_f1da537d301b_inactive.xml | 1 + ...v_c60cc60c_c60c_c60c_c60c_c60cc60cc60c.xml | 10 +++ ...c_c60c_c60c_c60c_c60cc60cc60c_inactive.xml | 9 +++ ...9_36ea_4111_8f0a_8c9a70e21366_inactive.xml | 1 + ...9_495e_4243_ad9f_beb3f14c23d9_inactive.xml | 1 + ...4_f554_4dc1_809d_b2a01e8e48ad_inactive.xml | 8 +++ ...6_1ca8_49ac_b176_871d16c13076_inactive.xml | 1 + tests/nodedevxml2xmltest.c | 59 +++++++++++++++--- 18 files changed, 197 insertions(+), 59 deletions(-) create mode 100644 tests/nodedevschemadata/mdev_c60cc60c_c60c_c60c_c60c_c60cc60cc60c.xml create mode 120000 tests/nodedevxml2xmlout/mdev_3627463d_b7f0_4fea_b468_f1da537d301b_inactive.xml create mode 100644 tests/nodedevxml2xmlout/mdev_c60cc60c_c60c_c60c_c60c_c60cc60cc60c.xml create mode 100644 tests/nodedevxml2xmlout/mdev_c60cc60c_c60c_c60c_c60c_c60cc60cc60c_inactive.xml create mode 120000 tests/nodedevxml2xmlout/mdev_d069d019_36ea_4111_8f0a_8c9a70e21366_inactive.xml create mode 120000 tests/nodedevxml2xmlout/mdev_d2441d39_495e_4243_ad9f_beb3f14c23d9_inactive.xml create mode 100644 tests/nodedevxml2xmlout/mdev_ee0b88c4_f554_4dc1_809d_b2a01e8e48ad_inactive.xml create mode 120000 tests/nodedevxml2xmlout/mdev_fedc4916_1ca8_49ac_b176_871d16c13076_inactive.xml diff --git a/include/libvirt/libvirt-nodedev.h b/include/libvirt/libvirt-nodedev.h index 428b0d722f..53ffce6c69 100644 --- a/include/libvirt/libvirt-nodedev.h +++ b/include/libvirt/libvirt-nodedev.h @@ -117,6 +117,17 @@ int virNodeDeviceListCaps (virNodeDevicePtr dev, char **const names, int maxnames); +/** + * virNodeDeviceXMLFlags: + * + * Flags used to provide the state of the returned node device configuration. + * + * Since: 10.1.0 + */ +typedef enum { + VIR_NODE_DEVICE_XML_INACTIVE = 1 << 0, /* dump inactive device configuration (Since: 10.1.0) */ +} virNodeDeviceXMLFlags; + char * virNodeDeviceGetXMLDesc (virNodeDevicePtr dev, unsigned int flags); diff --git a/src/conf/node_device_conf.c b/src/conf/node_device_conf.c index febc58afd1..4de29ef01f 100644 --- a/src/conf/node_device_conf.c +++ b/src/conf/node_device_conf.c @@ -602,16 +602,22 @@ virNodeDeviceCapMdevAttrFormat(virBuffer *buf, static void virNodeDeviceCapMdevDefFormat(virBuffer *buf, - const virNodeDevCapData *data) + const virNodeDevCapData *data, + bool defined) { - virBufferEscapeString(buf, "<type id='%s'/>\n", data->mdev.dev_config.type); + if (defined) + virBufferEscapeString(buf, "<type id='%s'/>\n", data->mdev.defined_config.type); + else + virBufferEscapeString(buf, "<type id='%s'/>\n", data->mdev.active_config.type); virBufferEscapeString(buf, "<uuid>%s</uuid>\n", data->mdev.uuid); virBufferEscapeString(buf, "<parent_addr>%s</parent_addr>\n", data->mdev.parent_addr); virBufferAsprintf(buf, "<iommuGroup number='%u'/>\n", data->mdev.iommuGroupNumber); - - virNodeDeviceCapMdevAttrFormat(buf, &data->mdev.dev_config); + if (defined) + virNodeDeviceCapMdevAttrFormat(buf, &data->mdev.defined_config); + else + virNodeDeviceCapMdevAttrFormat(buf, &data->mdev.active_config); } static void @@ -662,25 +668,28 @@ virNodeDeviceCapCSSDefFormat(virBuffer *buf, char * -virNodeDeviceDefFormat(const virNodeDeviceDef *def) +virNodeDeviceDefFormat(const virNodeDeviceDef *def, unsigned int flags) { g_auto(virBuffer) buf = VIR_BUFFER_INITIALIZER; virNodeDevCapsDef *caps; size_t i = 0; + bool inactive_state = flags & VIR_NODE_DEVICE_XML_INACTIVE; virBufferAddLit(&buf, "<device>\n"); virBufferAdjustIndent(&buf, 2); virBufferEscapeString(&buf, "<name>%s</name>\n", def->name); - virBufferEscapeString(&buf, "<path>%s</path>\n", def->sysfs_path); - virBufferEscapeString(&buf, "<devnode type='dev'>%s</devnode>\n", - def->devnode); - if (def->devlinks) { - for (i = 0; def->devlinks[i]; i++) - virBufferEscapeString(&buf, "<devnode type='link'>%s</devnode>\n", - def->devlinks[i]); + if (!inactive_state) { + virBufferEscapeString(&buf, "<path>%s</path>\n", def->sysfs_path); + virBufferEscapeString(&buf, "<devnode type='dev'>%s</devnode>\n", + def->devnode); + if (def->devlinks) { + for (i = 0; def->devlinks[i]; i++) + virBufferEscapeString(&buf, "<devnode type='link'>%s</devnode>\n", + def->devlinks[i]); + } } virBufferEscapeString(&buf, "<parent>%s</parent>\n", def->parent); - if (def->driver) { + if (def->driver && !inactive_state) { virBufferAddLit(&buf, "<driver>\n"); virBufferAdjustIndent(&buf, 2); virBufferEscapeString(&buf, "<name>%s</name>\n", def->driver); @@ -741,7 +750,7 @@ virNodeDeviceDefFormat(const virNodeDeviceDef *def) virBufferEscapeString(&buf, "<type>%s</type>\n", virNodeDevDRMTypeToString(data->drm.type)); break; case VIR_NODE_DEV_CAP_MDEV: - virNodeDeviceCapMdevDefFormat(&buf, data); + virNodeDeviceCapMdevDefFormat(&buf, data, inactive_state); break; case VIR_NODE_DEV_CAP_CCW_DEV: virNodeDeviceCapCCWDefFormat(&buf, data); @@ -2207,7 +2216,7 @@ virNodeDevCapMdevParseXML(xmlXPathContextPtr ctxt, ctxt->node = node; - if (!(mdev->dev_config.type = virXPathString("string(./type[1]/@id)", ctxt))) { + if (!(mdev->defined_config.type = virXPathString("string(./type[1]/@id)", ctxt))) { virReportError(VIR_ERR_CONFIG_UNSUPPORTED, _("missing type id attribute for '%1$s'"), def->name); return -1; @@ -2239,7 +2248,7 @@ virNodeDevCapMdevParseXML(xmlXPathContextPtr ctxt, return -1; for (i = 0; i < nattrs; i++) - virNodeDevCapMdevAttributeParseXML(ctxt, attrs[i], &mdev->dev_config); + virNodeDevCapMdevAttributeParseXML(ctxt, attrs[i], &mdev->defined_config); return 0; } @@ -2582,11 +2591,15 @@ virNodeDevCapsDefFree(virNodeDevCapsDef *caps) g_free(data->sg.path); break; case VIR_NODE_DEV_CAP_MDEV: - g_free(data->mdev.dev_config.type); + g_free(data->mdev.defined_config.type); + g_free(data->mdev.active_config.type); g_free(data->mdev.uuid); - for (i = 0; i < data->mdev.dev_config.nattributes; i++) - virMediatedDeviceAttrFree(data->mdev.dev_config.attributes[i]); - g_free(data->mdev.dev_config.attributes); + for (i = 0; i < data->mdev.defined_config.nattributes; i++) + virMediatedDeviceAttrFree(data->mdev.defined_config.attributes[i]); + g_free(data->mdev.defined_config.attributes); + for (i = 0; i < data->mdev.active_config.nattributes; i++) + virMediatedDeviceAttrFree(data->mdev.active_config.attributes[i]); + g_free(data->mdev.active_config.attributes); g_free(data->mdev.parent_addr); break; case VIR_NODE_DEV_CAP_CSS_DEV: diff --git a/src/conf/node_device_conf.h b/src/conf/node_device_conf.h index f100496272..f59440dbb9 100644 --- a/src/conf/node_device_conf.h +++ b/src/conf/node_device_conf.h @@ -153,7 +153,8 @@ typedef struct _virNodeDevCapMdev virNodeDevCapMdev; struct _virNodeDevCapMdev { unsigned int iommuGroupNumber; char *uuid; - virMediatedDeviceConfig dev_config; + virMediatedDeviceConfig defined_config; + virMediatedDeviceConfig active_config; char *parent_addr; bool autostart; }; @@ -360,7 +361,7 @@ struct _virNodeDeviceDef { }; char * -virNodeDeviceDefFormat(const virNodeDeviceDef *def); +virNodeDeviceDefFormat(const virNodeDeviceDef *def, unsigned int flags); typedef int (*virNodeDeviceDefPostParseCallback)(virNodeDeviceDef *dev, diff --git a/src/libvirt-nodedev.c b/src/libvirt-nodedev.c index f0f99bc020..c683b2eef9 100644 --- a/src/libvirt-nodedev.c +++ b/src/libvirt-nodedev.c @@ -264,7 +264,7 @@ virNodeDeviceLookupSCSIHostByWWN(virConnectPtr conn, /** * virNodeDeviceGetXMLDesc: * @dev: pointer to the node device - * @flags: extra flags; not used yet, so callers should always pass 0 + * @flags: bitwise-OR of virNodeDeviceXMLFlags * * Fetch an XML document describing all aspects of * the device. diff --git a/src/node_device/node_device_driver.c b/src/node_device/node_device_driver.c index 7118f833f6..baff49a0ae 100644 --- a/src/node_device/node_device_driver.c +++ b/src/node_device/node_device_driver.c @@ -338,7 +338,7 @@ nodeDeviceGetXMLDesc(virNodeDevicePtr device, virNodeDeviceDef *def; char *ret = NULL; - virCheckFlags(0, NULL); + virCheckFlags(VIR_NODE_DEVICE_XML_INACTIVE, NULL); if (nodeDeviceInitWait() < 0) return NULL; @@ -356,7 +356,19 @@ nodeDeviceGetXMLDesc(virNodeDevicePtr device, if (virNodeDeviceUpdateCaps(def) < 0) goto cleanup; - ret = virNodeDeviceDefFormat(def); + if (flags & VIR_NODE_DEVICE_XML_INACTIVE) { + if (!virNodeDeviceObjIsPersistent(obj)) { + virReportError(VIR_ERR_OPERATION_INVALID, + _("node device '%1$s' is not persistent"), + def->name); + goto cleanup; + } + } else { + if (!virNodeDeviceObjIsActive(obj)) + flags |= VIR_NODE_DEVICE_XML_INACTIVE; + } + + ret = virNodeDeviceDefFormat(def, flags); cleanup: virNodeDeviceObjEndAPI(&obj); @@ -629,19 +641,20 @@ nodeDeviceAttributesToJSON(virJSONValue *json, /* format a json string that provides configuration information about this mdev * to the mdevctl utility */ static int -nodeDeviceDefToMdevctlConfig(virNodeDeviceDef *def, char **buf) +nodeDeviceDefToMdevctlConfig(virNodeDeviceDef *def, char **buf, bool defined) { virNodeDevCapMdev *mdev = &def->caps->data.mdev; + virMediatedDeviceConfig *mdev_config = defined ? &mdev->defined_config : &mdev->active_config; g_autoptr(virJSONValue) json = virJSONValueNewObject(); const char *startval = mdev->autostart ? "auto" : "manual"; - if (virJSONValueObjectAppendString(json, "mdev_type", mdev->dev_config.type) < 0) + if (virJSONValueObjectAppendString(json, "mdev_type", mdev_config->type) < 0) return -1; if (virJSONValueObjectAppendString(json, "start", startval) < 0) return -1; - if (nodeDeviceAttributesToJSON(json, &mdev->dev_config) < 0) + if (nodeDeviceAttributesToJSON(json, mdev_config) < 0) return -1; *buf = virJSONValueToString(json, false); @@ -760,7 +773,7 @@ nodeDeviceGetMdevctlCommand(virNodeDeviceDef *def, return NULL; } - if (nodeDeviceDefToMdevctlConfig(def, &inbuf) < 0) { + if (nodeDeviceDefToMdevctlConfig(def, &inbuf, true) < 0) { virReportError(VIR_ERR_INTERNAL_ERROR, "%s", _("couldn't convert node device def to mdevctl JSON")); return NULL; @@ -1138,9 +1151,11 @@ nodeDeviceParseMdevctlAttributes(virMediatedDeviceConfig *config, static virNodeDeviceDef* nodeDeviceParseMdevctlChildDevice(const char *parent, - virJSONValue *json) + virJSONValue *json, + bool defined) { virNodeDevCapMdev *mdev; + virMediatedDeviceConfig *mdev_config; const char *uuid; virJSONValue *props; g_autoptr(virNodeDeviceDef) child = g_new0(virNodeDeviceDef, 1); @@ -1170,14 +1185,15 @@ nodeDeviceParseMdevctlChildDevice(const char *parent, child->caps->data.type = VIR_NODE_DEV_CAP_MDEV; mdev = &child->caps->data.mdev; + mdev_config = defined ? &mdev->defined_config : &mdev->active_config; mdev->uuid = g_strdup(uuid); mdev->parent_addr = g_strdup(parent); - mdev->dev_config.type = + mdev_config->type = g_strdup(virJSONValueObjectGetString(props, "mdev_type")); start = virJSONValueObjectGetString(props, "start"); mdev->autostart = STREQ_NULLABLE(start, "auto"); - if (nodeDeviceParseMdevctlAttributes(&mdev->dev_config, + if (nodeDeviceParseMdevctlAttributes(mdev_config, virJSONValueObjectGet(props, "attrs")) < 0) return NULL; @@ -1189,7 +1205,8 @@ nodeDeviceParseMdevctlChildDevice(const char *parent, int nodeDeviceParseMdevctlJSON(const char *jsonstring, - virNodeDeviceDef ***devs) + virNodeDeviceDef ***devs, + bool defined) { int n; g_autoptr(virJSONValue) json_devicelist = NULL; @@ -1259,7 +1276,7 @@ nodeDeviceParseMdevctlJSON(const char *jsonstring, g_autoptr(virNodeDeviceDef) child = NULL; virJSONValue *child_obj = virJSONValueArrayGet(child_array, j); - if (!(child = nodeDeviceParseMdevctlChildDevice(parent, child_obj))) { + if (!(child = nodeDeviceParseMdevctlChildDevice(parent, child_obj, defined))) { virReportError(VIR_ERR_INTERNAL_ERROR, "%s", _("Unable to parse child device")); goto error; @@ -1402,7 +1419,7 @@ nodeDeviceUpdateMediatedDevice(virNodeDeviceDef *def, /* Active devices contain some additional information (e.g. sysfs * path) that is not provided by mdevctl, so re-use the existing * definition and copy over new mdev data */ - changed = nodeDeviceDefCopyFromMdevctl(olddef, owned); + changed = nodeDeviceDefCopyFromMdevctl(olddef, owned, defined); if (was_defined && !changed) { /* if this device was already defined and the definition @@ -1672,7 +1689,7 @@ virMdevctlList(bool defined, return -1; } - return nodeDeviceParseMdevctlJSON(output, devs); + return nodeDeviceParseMdevctlJSON(output, devs, defined); } @@ -1829,16 +1846,24 @@ virMediatedDeviceAttrsCopy(virMediatedDeviceConfig *dst_config, * Returns true if anything was copied, else returns false */ bool nodeDeviceDefCopyFromMdevctl(virNodeDeviceDef *dst, - virNodeDeviceDef *src) + virNodeDeviceDef *src, + bool defined) { bool ret = false; virNodeDevCapMdev *srcmdev = &src->caps->data.mdev; virNodeDevCapMdev *dstmdev = &dst->caps->data.mdev; + virMediatedDeviceConfig *srcmdevconfig = &src->caps->data.mdev.active_config; + virMediatedDeviceConfig *dstmdevconfig = &dst->caps->data.mdev.active_config; + + if (defined) { + srcmdevconfig = &src->caps->data.mdev.defined_config; + dstmdevconfig = &dst->caps->data.mdev.defined_config; + } - if (STRNEQ_NULLABLE(dstmdev->dev_config.type, srcmdev->dev_config.type)) { + if (STRNEQ_NULLABLE(dstmdevconfig->type, srcmdevconfig->type)) { ret = true; - g_free(dstmdev->dev_config.type); - dstmdev->dev_config.type = g_strdup(srcmdev->dev_config.type); + g_free(dstmdevconfig->type); + dstmdevconfig->type = g_strdup(srcmdevconfig->type); } if (STRNEQ_NULLABLE(dstmdev->uuid, srcmdev->uuid)) { @@ -1847,7 +1872,7 @@ nodeDeviceDefCopyFromMdevctl(virNodeDeviceDef *dst, dstmdev->uuid = g_strdup(srcmdev->uuid); } - if (virMediatedDeviceAttrsCopy(&dstmdev->dev_config, &srcmdev->dev_config)) + if (virMediatedDeviceAttrsCopy(dstmdevconfig, srcmdevconfig)) ret = true; if (dstmdev->autostart != srcmdev->autostart) { diff --git a/src/node_device/node_device_driver.h b/src/node_device/node_device_driver.h index c7d5e22daf..4dce7e6f17 100644 --- a/src/node_device/node_device_driver.h +++ b/src/node_device/node_device_driver.h @@ -142,7 +142,8 @@ nodeDeviceGetMdevctlListCommand(bool defined, int nodeDeviceParseMdevctlJSON(const char *jsonstring, - virNodeDeviceDef ***devs); + virNodeDeviceDef ***devs, + bool defined); int nodeDeviceUpdateMediatedDevices(void); @@ -154,7 +155,8 @@ nodeDeviceGenerateName(virNodeDeviceDef *def, const char *s); bool nodeDeviceDefCopyFromMdevctl(virNodeDeviceDef *dst, - virNodeDeviceDef *src); + virNodeDeviceDef *src, + bool defined); int nodeDeviceCreate(virNodeDevice *dev, diff --git a/src/node_device/node_device_udev.c b/src/node_device/node_device_udev.c index 254e802c50..57368a96c3 100644 --- a/src/node_device/node_device_udev.c +++ b/src/node_device/node_device_udev.c @@ -1069,7 +1069,7 @@ udevProcessMediatedDevice(struct udev_device *dev, return -1; } - data->dev_config.type = g_path_get_basename(canonicalpath); + data->active_config.type = g_path_get_basename(canonicalpath); data->uuid = g_strdup(udev_device_get_sysname(dev)); if ((iommugrp = virMediatedDeviceGetIOMMUGroupNum(data->uuid)) < 0) @@ -1572,7 +1572,7 @@ udevAddOneDevice(struct udev_device *device) objdef = virNodeDeviceObjGetDef(obj); if (is_mdev) - nodeDeviceDefCopyFromMdevctl(def, objdef); + nodeDeviceDefCopyFromMdevctl(def, objdef, false); persistent = virNodeDeviceObjIsPersistent(obj); autostart = virNodeDeviceObjIsAutostart(obj); diff --git a/src/test/test_driver.c b/src/test/test_driver.c index ed545848af..ed0cdc0dab 100644 --- a/src/test/test_driver.c +++ b/src/test/test_driver.c @@ -7514,12 +7514,12 @@ testNodeDeviceGetXMLDesc(virNodeDevicePtr dev, virNodeDeviceObj *obj; char *ret = NULL; - virCheckFlags(0, NULL); + virCheckFlags(VIR_NODE_DEVICE_XML_INACTIVE, NULL); if (!(obj = testNodeDeviceObjFindByName(driver, dev->name))) return NULL; - ret = virNodeDeviceDefFormat(virNodeDeviceObjGetDef(obj)); + ret = virNodeDeviceDefFormat(virNodeDeviceObjGetDef(obj), flags); virNodeDeviceObjEndAPI(&obj); return ret; @@ -7619,7 +7619,7 @@ testNodeDeviceMockCreateVport(testDriver *driver, "scsi_host11"))) goto cleanup; - xml = virNodeDeviceDefFormat(virNodeDeviceObjGetDef(objcopy)); + xml = virNodeDeviceDefFormat(virNodeDeviceObjGetDef(objcopy), 0); virNodeDeviceObjEndAPI(&objcopy); if (!xml) goto cleanup; diff --git a/tests/nodedevmdevctltest.c b/tests/nodedevmdevctltest.c index e403328e5a..f49d668461 100644 --- a/tests/nodedevmdevctltest.c +++ b/tests/nodedevmdevctltest.c @@ -229,13 +229,13 @@ testMdevctlParse(const void *data) return -1; } - if ((nmdevs = nodeDeviceParseMdevctlJSON(buf, &mdevs)) < 0) { + if ((nmdevs = nodeDeviceParseMdevctlJSON(buf, &mdevs, true)) < 0) { VIR_TEST_DEBUG("Unable to parse json for %s", filename); return -1; } for (i = 0; i < nmdevs; i++) { - g_autofree char *devxml = virNodeDeviceDefFormat(mdevs[i]); + g_autofree char *devxml = virNodeDeviceDefFormat(mdevs[i], VIR_NODE_DEVICE_XML_INACTIVE); if (!devxml) goto cleanup; virBufferAddStr(&xmloutbuf, devxml); diff --git a/tests/nodedevschemadata/mdev_c60cc60c_c60c_c60c_c60c_c60cc60cc60c.xml b/tests/nodedevschemadata/mdev_c60cc60c_c60c_c60c_c60c_c60cc60cc60c.xml new file mode 100644 index 0000000000..6926559efa --- /dev/null +++ b/tests/nodedevschemadata/mdev_c60cc60c_c60c_c60c_c60c_c60cc60cc60c.xml @@ -0,0 +1,14 @@ +<device> + <name>mdev_c60cc60c_c60c_c60c_c60c_c60cc60cc60c_0_0_0052</name> + <path>/sys/devices/css0/0.0.0052/c60cc60c-c60c-c60c-c60c-c60cc60cc60c</path> + <parent>css_0_0_0052</parent> + <driver> + <name>vfio_ccw_mdev</name> + </driver> + <capability type='mdev'> + <type id='vfio_ccw-io'/> + <uuid>c60cc60c-c60c-c60c-c60c-c60cc60cc60c</uuid> + <parent_addr>0.0.0052</parent_addr> + <iommuGroup number='4'/> + </capability> +</device> diff --git a/tests/nodedevxml2xmlout/mdev_3627463d_b7f0_4fea_b468_f1da537d301b_inactive.xml b/tests/nodedevxml2xmlout/mdev_3627463d_b7f0_4fea_b468_f1da537d301b_inactive.xml new file mode 120000 index 0000000000..f8ec7d8a32 --- /dev/null +++ b/tests/nodedevxml2xmlout/mdev_3627463d_b7f0_4fea_b468_f1da537d301b_inactive.xml @@ -0,0 +1 @@ +mdev_3627463d_b7f0_4fea_b468_f1da537d301b.xml \ No newline at end of file diff --git a/tests/nodedevxml2xmlout/mdev_c60cc60c_c60c_c60c_c60c_c60cc60cc60c.xml b/tests/nodedevxml2xmlout/mdev_c60cc60c_c60c_c60c_c60c_c60cc60cc60c.xml new file mode 100644 index 0000000000..82c60cc065 --- /dev/null +++ b/tests/nodedevxml2xmlout/mdev_c60cc60c_c60c_c60c_c60c_c60cc60cc60c.xml @@ -0,0 +1,10 @@ +<device> + <name>mdev_c60cc60c_c60c_c60c_c60c_c60cc60cc60c_0_0_0052</name> + <path>/sys/devices/css0/0.0.0052/c60cc60c-c60c-c60c-c60c-c60cc60cc60c</path> + <parent>css_0_0_0052</parent> + <capability type='mdev'> + <type id='vfio_ccw-io'/> + <uuid>c60cc60c-c60c-c60c-c60c-c60cc60cc60c</uuid> + <iommuGroup number='4'/> + </capability> +</device> diff --git a/tests/nodedevxml2xmlout/mdev_c60cc60c_c60c_c60c_c60c_c60cc60cc60c_inactive.xml b/tests/nodedevxml2xmlout/mdev_c60cc60c_c60c_c60c_c60c_c60cc60cc60c_inactive.xml new file mode 100644 index 0000000000..87c5ed1340 --- /dev/null +++ b/tests/nodedevxml2xmlout/mdev_c60cc60c_c60c_c60c_c60c_c60cc60cc60c_inactive.xml @@ -0,0 +1,9 @@ +<device> + <name>mdev_c60cc60c_c60c_c60c_c60c_c60cc60cc60c_0_0_0052</name> + <parent>css_0_0_0052</parent> + <capability type='mdev'> + <type id='vfio_ccw-io'/> + <uuid>c60cc60c-c60c-c60c-c60c-c60cc60cc60c</uuid> + <iommuGroup number='4'/> + </capability> +</device> diff --git a/tests/nodedevxml2xmlout/mdev_d069d019_36ea_4111_8f0a_8c9a70e21366_inactive.xml b/tests/nodedevxml2xmlout/mdev_d069d019_36ea_4111_8f0a_8c9a70e21366_inactive.xml new file mode 120000 index 0000000000..f6d5789399 --- /dev/null +++ b/tests/nodedevxml2xmlout/mdev_d069d019_36ea_4111_8f0a_8c9a70e21366_inactive.xml @@ -0,0 +1 @@ +mdev_d069d019_36ea_4111_8f0a_8c9a70e21366.xml \ No newline at end of file diff --git a/tests/nodedevxml2xmlout/mdev_d2441d39_495e_4243_ad9f_beb3f14c23d9_inactive.xml b/tests/nodedevxml2xmlout/mdev_d2441d39_495e_4243_ad9f_beb3f14c23d9_inactive.xml new file mode 120000 index 0000000000..233a7506fb --- /dev/null +++ b/tests/nodedevxml2xmlout/mdev_d2441d39_495e_4243_ad9f_beb3f14c23d9_inactive.xml @@ -0,0 +1 @@ +mdev_d2441d39_495e_4243_ad9f_beb3f14c23d9.xml \ No newline at end of file diff --git a/tests/nodedevxml2xmlout/mdev_ee0b88c4_f554_4dc1_809d_b2a01e8e48ad_inactive.xml b/tests/nodedevxml2xmlout/mdev_ee0b88c4_f554_4dc1_809d_b2a01e8e48ad_inactive.xml new file mode 100644 index 0000000000..8c4983f43c --- /dev/null +++ b/tests/nodedevxml2xmlout/mdev_ee0b88c4_f554_4dc1_809d_b2a01e8e48ad_inactive.xml @@ -0,0 +1,8 @@ +<device> + <name>mdev_ee0b88c4-f554-4dc1-809d-b2a01e8e48ad</name> + <parent>ap_matrix</parent> + <capability type='mdev'> + <type id='vfio_ap-passthrough'/> + <iommuGroup number='0'/> + </capability> +</device> diff --git a/tests/nodedevxml2xmlout/mdev_fedc4916_1ca8_49ac_b176_871d16c13076_inactive.xml b/tests/nodedevxml2xmlout/mdev_fedc4916_1ca8_49ac_b176_871d16c13076_inactive.xml new file mode 120000 index 0000000000..e053844177 --- /dev/null +++ b/tests/nodedevxml2xmlout/mdev_fedc4916_1ca8_49ac_b176_871d16c13076_inactive.xml @@ -0,0 +1 @@ +mdev_fedc4916_1ca8_49ac_b176_871d16c13076.xml \ No newline at end of file diff --git a/tests/nodedevxml2xmltest.c b/tests/nodedevxml2xmltest.c index 068ec68769..10e88f7779 100644 --- a/tests/nodedevxml2xmltest.c +++ b/tests/nodedevxml2xmltest.c @@ -11,14 +11,20 @@ #define VIR_FROM_THIS VIR_FROM_NONE +struct TestData { + const char *filename; + unsigned int flags; +}; + static int -testCompareXMLToXMLFiles(const char *xml, const char *outfile) +testCompareXMLToXMLFiles(const char *xml, const char *outfile, unsigned int flags) { g_autofree char *xmlData = NULL; g_autofree char *actual = NULL; int ret = -1; virNodeDeviceDef *dev = NULL; virNodeDevCapsDef *caps; + size_t i; if (virTestLoadFile(xml, &xmlData) < 0) goto fail; @@ -46,9 +52,22 @@ testCompareXMLToXMLFiles(const char *xml, const char *outfile) data->storage.logical_block_size; } } + + if (caps->data.type == VIR_NODE_DEV_CAP_MDEV && + !(flags & VIR_NODE_DEVICE_XML_INACTIVE)) { + data->mdev.active_config.type = g_strdup(data->mdev.defined_config.type); + for (i = 0; i < data->mdev.defined_config.nattributes; i++) { + g_autoptr(virMediatedDeviceAttr) attr = g_new0(virMediatedDeviceAttr, 1); + attr->name = g_strdup(data->mdev.defined_config.attributes[i]->name); + attr->value = g_strdup(data->mdev.defined_config.attributes[i]->value); + VIR_APPEND_ELEMENT(data->mdev.active_config.attributes, + data->mdev.active_config.nattributes, + attr); + } + } } - if (!(actual = virNodeDeviceDefFormat(dev))) + if (!(actual = virNodeDeviceDefFormat(dev, flags))) goto fail; if (virTestCompareToFile(actual, outfile) < 0) @@ -65,16 +84,21 @@ static int testCompareXMLToXMLHelper(const void *data) { int result = -1; + const struct TestData *tdata = data; g_autofree char *xml = NULL; g_autofree char *outfile = NULL; xml = g_strdup_printf("%s/nodedevschemadata/%s.xml", abs_srcdir, - (const char *)data); + tdata->filename); - outfile = g_strdup_printf("%s/nodedevxml2xmlout/%s.xml", abs_srcdir, - (const char *)data); + if (tdata->flags & VIR_NODE_DEVICE_XML_INACTIVE) + outfile = g_strdup_printf("%s/nodedevxml2xmlout/%s_inactive.xml", abs_srcdir, + tdata->filename); + else + outfile = g_strdup_printf("%s/nodedevxml2xmlout/%s.xml", abs_srcdir, + tdata->filename); - result = testCompareXMLToXMLFiles(xml, outfile); + result = testCompareXMLToXMLFiles(xml, outfile, tdata->flags); return result; } @@ -85,10 +109,20 @@ mymain(void) { int ret = 0; +#define DO_TEST_FLAGS(desc, filename, flags) \ + do { \ + struct TestData data = { filename, flags }; \ + if (virTestRun(desc, testCompareXMLToXMLHelper, &data) < 0) \ + ret = -1; \ + } \ + while (0) + #define DO_TEST(name) \ - if (virTestRun("Node device XML-2-XML " name, \ - testCompareXMLToXMLHelper, (name)) < 0) \ - ret = -1 + DO_TEST_FLAGS("Node device XML-2-XML " name, name, 0) + +#define DO_TEST_INACTIVE(name) \ + DO_TEST_FLAGS("Node device XML-2-XML INACTIVE " name, \ + name, VIR_NODE_DEVICE_XML_INACTIVE) DO_TEST("computer"); DO_TEST("DVD_GCC_4247N"); @@ -121,6 +155,7 @@ mymain(void) DO_TEST("pci_0000_02_10_7_mdev_types"); DO_TEST("pci_0000_42_00_0_vpd"); DO_TEST("mdev_3627463d_b7f0_4fea_b468_f1da537d301b"); + DO_TEST_INACTIVE("mdev_3627463d_b7f0_4fea_b468_f1da537d301b"); DO_TEST("ccw_0_0_ffff"); DO_TEST("css_0_0_ffff"); DO_TEST("css_0_0_ffff_channel_dev_addr"); @@ -134,7 +169,13 @@ mymain(void) DO_TEST("mdev_d069d019_36ea_4111_8f0a_8c9a70e21366"); DO_TEST("mdev_d2441d39_495e_4243_ad9f_beb3f14c23d9"); DO_TEST("mdev_fedc4916_1ca8_49ac_b176_871d16c13076"); + DO_TEST_INACTIVE("mdev_ee0b88c4_f554_4dc1_809d_b2a01e8e48ad"); + DO_TEST_INACTIVE("mdev_d069d019_36ea_4111_8f0a_8c9a70e21366"); + DO_TEST_INACTIVE("mdev_d2441d39_495e_4243_ad9f_beb3f14c23d9"); + DO_TEST_INACTIVE("mdev_fedc4916_1ca8_49ac_b176_871d16c13076"); DO_TEST("hba_vport_ops"); + DO_TEST("mdev_c60cc60c_c60c_c60c_c60c_c60cc60cc60c"); + DO_TEST_INACTIVE("mdev_c60cc60c_c60c_c60c_c60c_c60cc60cc60c"); return ret == 0 ? EXIT_SUCCESS : EXIT_FAILURE; } -- 2.42.0

Allow to dump the XML of the persisted mdev when the mdev has been started instead of the current state only. Signed-off-by: Boris Fiuczynski <fiuczy@linux.ibm.com> Reviewed-by: Jonathon Jongsma <jjongsma@redhat.com> --- docs/manpages/virsh.rst | 7 +++++-- tools/virsh-nodedev.c | 10 +++++++++- 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/docs/manpages/virsh.rst b/docs/manpages/virsh.rst index ed1027e133..3a814dccd2 100644 --- a/docs/manpages/virsh.rst +++ b/docs/manpages/virsh.rst @@ -5422,14 +5422,17 @@ nodedev-dumpxml :: - nodedev-dumpxml [--xpath EXPRESSION] [--wrap] device + nodedev-dumpxml [--inactive] [--xpath EXPRESSION] [--wrap] device Dump a <device> XML representation for the given node device, including such information as the device name, which bus owns the device, the vendor and product id, and any capabilities of the device usable by libvirt (such as whether device reset is supported). *device* can be either device name or wwn pair in "wwnn,wwpn" format (only works -for HBA). +for HBA). An additional option affecting the XML dump may be +used. *--inactive* tells virsh to dump the node device configuration +that will be used on next start of the node device as opposed to the +current node device configuration. If the **--xpath** argument provides an XPath expression, it will be evaluated against the output XML and only those matching nodes will diff --git a/tools/virsh-nodedev.c b/tools/virsh-nodedev.c index fb38fd7fcc..aeb3685aca 100644 --- a/tools/virsh-nodedev.c +++ b/tools/virsh-nodedev.c @@ -575,6 +575,10 @@ static const vshCmdOptDef opts_node_device_dumpxml[] = { .help = N_("device name or wwn pair in 'wwnn,wwpn' format"), .completer = virshNodeDeviceNameCompleter, }, + {.name = "inactive", + .type = VSH_OT_BOOL, + .help = N_("show inactive defined XML"), + }, {.name = "xpath", .type = VSH_OT_STRING, .flags = VSH_OFLAG_REQ_OPT, @@ -594,6 +598,7 @@ cmdNodeDeviceDumpXML(vshControl *ctl, const vshCmd *cmd) g_autoptr(virshNodeDevice) device = NULL; g_autofree char *xml = NULL; const char *device_value = NULL; + unsigned int flags = 0; bool wrap = vshCommandOptBool(cmd, "wrap"); const char *xpath = NULL; @@ -608,7 +613,10 @@ cmdNodeDeviceDumpXML(vshControl *ctl, const vshCmd *cmd) if (!device) return false; - if (!(xml = virNodeDeviceGetXMLDesc(device, 0))) + if (vshCommandOptBool(cmd, "inactive")) + flags |= VIR_NODE_DEVICE_XML_INACTIVE; + + if (!(xml = virNodeDeviceGetXMLDesc(device, flags))) return false; return virshDumpXML(ctl, xml, "node-device", xpath, wrap); -- 2.42.0

Allow to filter node devices based on their persisted or transient states. Signed-off-by: Boris Fiuczynski <fiuczy@linux.ibm.com> Reviewed-by: Jonathon Jongsma <jjongsma@redhat.com> --- include/libvirt/libvirt-nodedev.h | 2 ++ src/conf/node_device_conf.h | 7 ++++++- src/conf/virnodedeviceobj.c | 8 ++++++++ 3 files changed, 16 insertions(+), 1 deletion(-) diff --git a/include/libvirt/libvirt-nodedev.h b/include/libvirt/libvirt-nodedev.h index 53ffce6c69..f7ddbfa4ad 100644 --- a/include/libvirt/libvirt-nodedev.h +++ b/include/libvirt/libvirt-nodedev.h @@ -91,6 +91,8 @@ typedef enum { VIR_CONNECT_LIST_NODE_DEVICES_CAP_AP_MATRIX = 1 << 20, /* s390 AP Matrix (Since: 7.0.0) */ VIR_CONNECT_LIST_NODE_DEVICES_CAP_VPD = 1 << 21, /* Device with VPD (Since: 7.9.0) */ + VIR_CONNECT_LIST_NODE_DEVICES_PERSISTENT = 1 << 28, /* Persisted devices (Since: 10.1.0) */ + VIR_CONNECT_LIST_NODE_DEVICES_TRANSIENT = 1 << 29, /* Transient devices (Since: 10.1.0) */ VIR_CONNECT_LIST_NODE_DEVICES_INACTIVE = 1 << 30, /* Inactive devices (Since: 7.3.0) */ VIR_CONNECT_LIST_NODE_DEVICES_ACTIVE = 1U << 31, /* Active devices (Since: 7.3.0) */ } virConnectListAllNodeDeviceFlags; diff --git a/src/conf/node_device_conf.h b/src/conf/node_device_conf.h index f59440dbb9..f0a5333881 100644 --- a/src/conf/node_device_conf.h +++ b/src/conf/node_device_conf.h @@ -432,9 +432,14 @@ G_DEFINE_AUTOPTR_CLEANUP_FUNC(virNodeDevCapsDef, virNodeDevCapsDefFree); VIR_CONNECT_LIST_NODE_DEVICES_ACTIVE | \ VIR_CONNECT_LIST_NODE_DEVICES_INACTIVE +#define VIR_CONNECT_LIST_NODE_DEVICES_FILTERS_PERSISTENT \ + VIR_CONNECT_LIST_NODE_DEVICES_PERSISTENT | \ + VIR_CONNECT_LIST_NODE_DEVICES_TRANSIENT + #define VIR_CONNECT_LIST_NODE_DEVICES_FILTERS_ALL \ VIR_CONNECT_LIST_NODE_DEVICES_FILTERS_CAP | \ - VIR_CONNECT_LIST_NODE_DEVICES_FILTERS_ACTIVE + VIR_CONNECT_LIST_NODE_DEVICES_FILTERS_ACTIVE | \ + VIR_CONNECT_LIST_NODE_DEVICES_FILTERS_PERSISTENT int virNodeDeviceGetSCSIHostCaps(virNodeDevCapSCSIHost *scsi_host); diff --git a/src/conf/virnodedeviceobj.c b/src/conf/virnodedeviceobj.c index cfef30d47e..31ec4249d8 100644 --- a/src/conf/virnodedeviceobj.c +++ b/src/conf/virnodedeviceobj.c @@ -911,6 +911,14 @@ virNodeDeviceObjMatch(virNodeDeviceObj *obj, return false; } + if (flags & (VIR_CONNECT_LIST_NODE_DEVICES_FILTERS_PERSISTENT)) { + if (!((MATCH(VIR_CONNECT_LIST_NODE_DEVICES_PERSISTENT) && + virNodeDeviceObjIsPersistent(obj)) || + (MATCH(VIR_CONNECT_LIST_NODE_DEVICES_TRANSIENT) && + !virNodeDeviceObjIsPersistent(obj)))) + return false; + } + return true; } #undef MATCH -- 2.42.0

Now that we can filter persisted and transient node devices in virConnectListAllNodeDevices(), add these switches also to the virsh nodedev-list command. Signed-off-by: Boris Fiuczynski <fiuczy@linux.ibm.com> Reviewed-by: Jonathon Jongsma <jjongsma@redhat.com> --- docs/manpages/virsh.rst | 8 ++++++-- tools/virsh-nodedev.c | 24 ++++++++++++++++++++++-- 2 files changed, 28 insertions(+), 4 deletions(-) diff --git a/docs/manpages/virsh.rst b/docs/manpages/virsh.rst index 3a814dccd2..248807d0e9 100644 --- a/docs/manpages/virsh.rst +++ b/docs/manpages/virsh.rst @@ -5461,7 +5461,7 @@ nodedev-list :: - nodedev-list [--cap capability] [--tree] [--inactive | --all] + nodedev-list [--cap capability] [--tree] [--inactive | --all] [--persistent | --transient] List all of the devices available on the node that are known by libvirt. *cap* is used to filter the list by capability types, the types must be @@ -5470,7 +5470,11 @@ separated by comma, e.g. --cap pci,scsi. Valid capability types include 'scsi', 'storage', 'fc_host', 'vports', 'scsi_generic', 'drm', 'mdev', 'mdev_types', 'ccw', 'css', 'ap_card', 'ap_queue', 'ap_matrix'. By default, only active devices are listed. *--inactive* is used to list only inactive -devices, and *-all* is used to list both active and inactive devices. +devices, and *--all* is used to list both active and inactive devices. +*--persistent* is used to list only persistent devices, and *--transient* is +used to list only transient devices. Not providing *--persistent* or +*--transient* will list all devices unless filtered otherwise. *--transient* +is mutually exclusive with *--persistent* and *--inactive*. If *--tree* is used, the output is formatted in a tree representing parents of each node. *--tree* is mutually exclusive with all other options. diff --git a/tools/virsh-nodedev.c b/tools/virsh-nodedev.c index aeb3685aca..5942c43689 100644 --- a/tools/virsh-nodedev.c +++ b/tools/virsh-nodedev.c @@ -390,6 +390,14 @@ static const vshCmdOptDef opts_node_list_devices[] = { .type = VSH_OT_BOOL, .help = N_("list inactive & active devices") }, + {.name = "persistent", + .type = VSH_OT_BOOL, + .help = N_("list persistent devices") + }, + {.name = "transient", + .type = VSH_OT_BOOL, + .help = N_("list transient devices") + }, {.name = NULL} }; @@ -407,6 +415,8 @@ cmdNodeListDevices(vshControl *ctl, const vshCmd *cmd G_GNUC_UNUSED) int cap_type = -1; bool inactive = vshCommandOptBool(cmd, "inactive"); bool all = vshCommandOptBool(cmd, "all"); + bool persistent = vshCommandOptBool(cmd, "persistent"); + bool transient = vshCommandOptBool(cmd, "transient"); ignore_value(vshCommandOptStringQuiet(ctl, cmd, "cap", &cap_str)); @@ -420,8 +430,13 @@ cmdNodeListDevices(vshControl *ctl, const vshCmd *cmd G_GNUC_UNUSED) return false; } - if (tree && (cap_str || inactive)) { - vshError(ctl, "%s", _("Option --tree is incompatible with --cap and --inactive")); + if (transient && (persistent || inactive)) { + vshError(ctl, "%s", _("Option --transient is incompatible with --persistent and --inactive")); + return false; + } + + if (tree && (cap_str || inactive || persistent || transient)) { + vshError(ctl, "%s", _("Option --tree is incompatible with --cap, --inactive, --persistent and --transient")); return false; } @@ -509,6 +524,11 @@ cmdNodeListDevices(vshControl *ctl, const vshCmd *cmd G_GNUC_UNUSED) if (!inactive) flags |= VIR_CONNECT_LIST_NODE_DEVICES_ACTIVE; + if (persistent) + flags |= VIR_CONNECT_LIST_NODE_DEVICES_PERSISTENT; + if (transient) + flags |= VIR_CONNECT_LIST_NODE_DEVICES_TRANSIENT; + if (!(list = virshNodeDeviceListCollect(ctl, caps, ncaps, flags))) { ret = false; goto cleanup; -- 2.42.0

Commit 26136e3 allowed to use option all with option tree but did not update the manpage. Signed-off-by: Boris Fiuczynski <fiuczy@linux.ibm.com> --- docs/manpages/virsh.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/manpages/virsh.rst b/docs/manpages/virsh.rst index 248807d0e9..bc0ad6542b 100644 --- a/docs/manpages/virsh.rst +++ b/docs/manpages/virsh.rst @@ -5476,7 +5476,7 @@ used to list only transient devices. Not providing *--persistent* or *--transient* will list all devices unless filtered otherwise. *--transient* is mutually exclusive with *--persistent* and *--inactive*. If *--tree* is used, the output is formatted in a tree representing parents of -each node. *--tree* is mutually exclusive with all other options. +each node. *--tree* is mutually exclusive with all other options but *--all*. nodedev-reattach -- 2.42.0

This public API is implemented for almost all other objects that have a concept of persistent definition and activatability. Node devices (mdevs) that can be defined and inactive, it will be useful to be able to update defined and active node devices as well. Signed-off-by: Boris Fiuczynski <fiuczy@linux.ibm.com> --- include/libvirt/libvirt-nodedev.h | 18 +++++++++++++ src/access/viraccessperm.c | 1 + src/access/viraccessperm.h | 6 +++++ src/conf/virnodedeviceobj.c | 42 +++++++++++++++++++++++++++++ src/conf/virnodedeviceobj.h | 3 +++ src/driver-nodedev.h | 6 +++++ src/libvirt-nodedev.c | 45 +++++++++++++++++++++++++++++++ src/libvirt_private.syms | 1 + src/libvirt_public.syms | 5 ++++ src/remote/remote_driver.c | 1 + src/remote/remote_protocol.x | 17 +++++++++++- src/remote_protocol-structs | 6 +++++ 12 files changed, 150 insertions(+), 1 deletion(-) diff --git a/include/libvirt/libvirt-nodedev.h b/include/libvirt/libvirt-nodedev.h index f7ddbfa4ad..de1d5bcbc1 100644 --- a/include/libvirt/libvirt-nodedev.h +++ b/include/libvirt/libvirt-nodedev.h @@ -188,6 +188,24 @@ int virNodeDeviceIsPersistent(virNodeDevicePtr dev); int virNodeDeviceIsActive(virNodeDevicePtr dev); +/** + * virNodeDeviceUpdateXMLFlags: + * + * Flags to control options for virNodeDeviceUpdateXML() + * + * Since: 10.1.0 + */ +typedef enum { + VIR_NODE_DEVICE_UPDATE_XML_AFFECT_CURRENT = 0, /* affect live if node device is active, + config if it's not active (Since: 10.1.0) */ + VIR_NODE_DEVICE_UPDATE_XML_AFFECT_LIVE = 1 << 0, /* affect live state of node device only (Since: 10.1.0) */ + VIR_NODE_DEVICE_UPDATE_XML_AFFECT_CONFIG = 1 << 1, /* affect persistent config only (Since: 10.1.0) */ +} virNodeDeviceUpdateXMLFlags; + +int virNodeDeviceUpdateXML(virNodeDevicePtr dev, + const char *xmlDesc, + unsigned int flags); + /** * VIR_NODE_DEVICE_EVENT_CALLBACK: * diff --git a/src/access/viraccessperm.c b/src/access/viraccessperm.c index d4a0c98b9b..702bee761d 100644 --- a/src/access/viraccessperm.c +++ b/src/access/viraccessperm.c @@ -71,6 +71,7 @@ VIR_ENUM_IMPL(virAccessPermNodeDevice, "getattr", "read", "write", "start", "stop", "detach", "delete", + "save", ); VIR_ENUM_IMPL(virAccessPermNWFilter, diff --git a/src/access/viraccessperm.h b/src/access/viraccessperm.h index 2f04459ed9..6cc2140c67 100644 --- a/src/access/viraccessperm.h +++ b/src/access/viraccessperm.h @@ -507,6 +507,12 @@ typedef enum { */ VIR_ACCESS_PERM_NODE_DEVICE_DELETE, + /** + * @desc: Save node device + * @message: Saving node device driver requires authorization + */ + VIR_ACCESS_PERM_NODE_DEVICE_SAVE, + VIR_ACCESS_PERM_NODE_DEVICE_LAST } virAccessPermNodeDevice; diff --git a/src/conf/virnodedeviceobj.c b/src/conf/virnodedeviceobj.c index 31ec4249d8..a8b1b625a7 100644 --- a/src/conf/virnodedeviceobj.c +++ b/src/conf/virnodedeviceobj.c @@ -1137,3 +1137,45 @@ virNodeDeviceObjListFind(virNodeDeviceObjList *devs, virNodeDeviceObjListFindHelper, &data); } + + +/** + * virNodeDeviceObjUpdateModificationImpact: + * @obj: Pointer to node device object + * @flags: flags to update the modification impact on + * + * Resolves virNodeDeviceObjUpdateModificationImpact flags in @flags so that + * they correctly apply to the actual state of @obj. @flags may be modified + * after call to this function. + * + * Returns 0 on success if @flags point to a valid combination for @obj or -1 + * on error. + */ +int +virNodeDeviceObjUpdateModificationImpact(virNodeDeviceObj *obj, + unsigned int *flags) +{ + bool isActive = virNodeDeviceObjIsActive(obj); + + if ((*flags & (VIR_NODE_DEVICE_UPDATE_XML_AFFECT_LIVE | VIR_NODE_DEVICE_UPDATE_XML_AFFECT_CONFIG)) == + VIR_NODE_DEVICE_UPDATE_XML_AFFECT_CURRENT) { + if (isActive) + *flags |= VIR_NODE_DEVICE_UPDATE_XML_AFFECT_LIVE; + else + *flags |= VIR_NODE_DEVICE_UPDATE_XML_AFFECT_CONFIG; + } + + if (!isActive && (*flags & VIR_NODE_DEVICE_UPDATE_XML_AFFECT_LIVE)) { + virReportError(VIR_ERR_OPERATION_INVALID, "%s", + _("mdev is not active")); + return -1; + } + + if (!virNodeDeviceObjIsPersistent(obj) && (*flags & VIR_NODE_DEVICE_UPDATE_XML_AFFECT_CONFIG)) { + virReportError(VIR_ERR_OPERATION_INVALID, "%s", + _("transient mdevs do not have any persistent config")); + return -1; + } + + return 0; +} diff --git a/src/conf/virnodedeviceobj.h b/src/conf/virnodedeviceobj.h index ba2e424998..43889b8dc8 100644 --- a/src/conf/virnodedeviceobj.h +++ b/src/conf/virnodedeviceobj.h @@ -159,3 +159,6 @@ virNodeDeviceObj * virNodeDeviceObjListFind(virNodeDeviceObjList *devs, virNodeDeviceObjListPredicate callback, void *opaque); + +int virNodeDeviceObjUpdateModificationImpact(virNodeDeviceObj *obj, + unsigned int *flags); diff --git a/src/driver-nodedev.h b/src/driver-nodedev.h index 167a8166dd..1c6a731f4e 100644 --- a/src/driver-nodedev.h +++ b/src/driver-nodedev.h @@ -101,6 +101,11 @@ typedef int typedef int (*virDrvNodeDeviceIsActive)(virNodeDevicePtr dev); +typedef int +(*virDrvNodeDeviceUpdateXML)(virNodeDevicePtr dev, + const char *xmlDesc, + unsigned int flags); + typedef int (*virDrvConnectNodeDeviceEventRegisterAny)(virConnectPtr conn, virNodeDevicePtr dev, @@ -146,4 +151,5 @@ struct _virNodeDeviceDriver { virDrvNodeDeviceGetAutostart nodeDeviceGetAutostart; virDrvNodeDeviceIsPersistent nodeDeviceIsPersistent; virDrvNodeDeviceIsActive nodeDeviceIsActive; + virDrvNodeDeviceUpdateXML nodeDeviceUpdateXML; }; diff --git a/src/libvirt-nodedev.c b/src/libvirt-nodedev.c index c683b2eef9..064ee48650 100644 --- a/src/libvirt-nodedev.c +++ b/src/libvirt-nodedev.c @@ -1174,3 +1174,48 @@ int virNodeDeviceIsActive(virNodeDevicePtr dev) virDispatchError(dev->conn); return -1; } + + +/** + * virNodeDeviceUpdateXML: + * @dev: pointer to the node device object + * @xmlDesc: string containing an XML description of the device to be defined + * @flags: bitwise OR of virNodeDeviceUpdateXMLFlags + * + * Update the definition of an existing node device, either its live + * running configuration, its persistent configuration, or both. + * + * Returns 0 in case of success, -1 in case of error + * + * Since: 10.1.0 + */ +int +virNodeDeviceUpdateXML(virNodeDevicePtr dev, + const char *xmlDesc, + unsigned int flags) +{ + VIR_DEBUG("nodeDevice=%p, xmlDesc=%s, flags=0x%x", + dev, NULLSTR(xmlDesc), flags); + + virResetLastError(); + + virCheckNodeDeviceReturn(dev, -1); + + virCheckReadOnlyGoto(dev->conn->flags, error); + virCheckNonNullArgGoto(xmlDesc, error); + + if (dev->conn->nodeDeviceDriver && + dev->conn->nodeDeviceDriver->nodeDeviceUpdateXML) { + int retval = dev->conn->nodeDeviceDriver->nodeDeviceUpdateXML(dev, xmlDesc, flags); + if (retval < 0) + goto error; + + return 0; + } + + virReportUnsupportedError(); + + error: + virDispatchError(dev->conn); + return -1; +} diff --git a/src/libvirt_private.syms b/src/libvirt_private.syms index c398371734..fae873aba0 100644 --- a/src/libvirt_private.syms +++ b/src/libvirt_private.syms @@ -1373,6 +1373,7 @@ virNodeDeviceObjListRemoveLocked; virNodeDeviceObjSetActive; virNodeDeviceObjSetAutostart; virNodeDeviceObjSetPersistent; +virNodeDeviceObjUpdateModificationImpact; # conf/virnwfilterbindingdef.h diff --git a/src/libvirt_public.syms b/src/libvirt_public.syms index bd1e916d2a..8b53eeb2ee 100644 --- a/src/libvirt_public.syms +++ b/src/libvirt_public.syms @@ -938,4 +938,9 @@ LIBVIRT_9.7.0 { virNetworkSetMetadata; } LIBVIRT_9.0.0; +LIBVIRT_10.1.0 { + global: + virNodeDeviceUpdateXML; +} LIBVIRT_9.7.0; + # .... define new API here using predicted next version number .... diff --git a/src/remote/remote_driver.c b/src/remote/remote_driver.c index bedf2cb833..ca7e125141 100644 --- a/src/remote/remote_driver.c +++ b/src/remote/remote_driver.c @@ -7983,6 +7983,7 @@ static virNodeDeviceDriver node_device_driver = { .nodeDeviceSetAutostart = remoteNodeDeviceSetAutostart, /* 7.8.0 */ .nodeDeviceIsPersistent = remoteNodeDeviceIsPersistent, /* 7.8.0 */ .nodeDeviceIsActive = remoteNodeDeviceIsActive, /* 7.8.0 */ + .nodeDeviceUpdateXML = remoteNodeDeviceUpdateXML, /* 10.1.0 */ }; static virNWFilterDriver nwfilter_driver = { diff --git a/src/remote/remote_protocol.x b/src/remote/remote_protocol.x index e295b0acc3..e95f672daf 100644 --- a/src/remote/remote_protocol.x +++ b/src/remote/remote_protocol.x @@ -2237,6 +2237,12 @@ struct remote_node_device_is_active_ret { int active; }; +struct remote_node_device_update_xml_args { + remote_nonnull_string name; + remote_nonnull_string xml_desc; + unsigned int flags; +}; + /* * Events Register/Deregister: @@ -7021,5 +7027,14 @@ enum remote_procedure { * @generate: both * @acl: none */ - REMOTE_PROC_NETWORK_EVENT_CALLBACK_METADATA_CHANGE = 446 + REMOTE_PROC_NETWORK_EVENT_CALLBACK_METADATA_CHANGE = 446, + + /** + * @generate: both + * @priority: high + * @acl: node_device:write + * @acl: node_device:save:!VIR_NODE_DEVICE_UPDATE_XML_AFFECT_CONFIG|VIR_NODE_DEVICE_UPDATE_XML_AFFECT_LIVE + * @acl: node_device:save:VIR_NODE_DEVICE_UPDATE_XML_AFFECT_CONFIG + */ + REMOTE_PROC_NODE_DEVICE_UPDATE_XML = 447 }; diff --git a/src/remote_protocol-structs b/src/remote_protocol-structs index 924ca41825..9a1b881caf 100644 --- a/src/remote_protocol-structs +++ b/src/remote_protocol-structs @@ -1673,6 +1673,11 @@ struct remote_node_device_is_active_args { struct remote_node_device_is_active_ret { int active; }; +struct remote_node_device_update_xml_args { + remote_nonnull_string name; + remote_nonnull_string xml_desc; + u_int flags; +}; struct remote_connect_domain_event_register_ret { int cb_registered; }; @@ -3743,4 +3748,5 @@ enum remote_procedure { REMOTE_PROC_NETWORK_SET_METADATA = 444, REMOTE_PROC_NETWORK_GET_METADATA = 445, REMOTE_PROC_NETWORK_EVENT_CALLBACK_METADATA_CHANGE = 446, + REMOTE_PROC_NODE_DEVICE_UPDATE_XML = 447, }; -- 2.42.0

On 2/7/24 7:39 AM, Boris Fiuczynski wrote:
This public API is implemented for almost all other objects that have a concept of persistent definition and activatability. Node devices (mdevs) that can be defined and inactive, it will be useful to be able to update defined and active node devices as well.
It looks like my previous comment on this patch might not have been sent to the list, so I'll repeat it here: In the commit summary, you talk about virNodeDeviceUpdate(), however, in the code you actually name the function virNodeDeviceUpdateXML(). There are no other public objects that provide a $(OBJECT)UpdateXML() function. The only other object that has a similar API is virNetworkUpdate(). That doesn't seem to match your claim that "this public API is implemented for almost all other objects...". What API were you referring to exactly? I think for API consistency, perhaps the Update() name is better than UpdateXML() to match virNetworkUpdate()? Regardless, the commit message should match the function name. Tangentially related: Almost all of the other API objects can update their persistent definition from an XML file by calling the $(OBJECT)DefineXML command to replace the existing definition. The Node device API does not yet support doing that, maybe we should strive for a bit more API consistency here and implement that as well. Clearly the DefineXML() API will only operate on a persistent definition, so if we want to provide a way to update a live device, we'll need a new API, which is presumably where this one comes in...
Signed-off-by: Boris Fiuczynski <fiuczy@linux.ibm.com> --- include/libvirt/libvirt-nodedev.h | 18 +++++++++++++ src/access/viraccessperm.c | 1 + src/access/viraccessperm.h | 6 +++++ src/conf/virnodedeviceobj.c | 42 +++++++++++++++++++++++++++++ src/conf/virnodedeviceobj.h | 3 +++ src/driver-nodedev.h | 6 +++++ src/libvirt-nodedev.c | 45 +++++++++++++++++++++++++++++++ src/libvirt_private.syms | 1 + src/libvirt_public.syms | 5 ++++ src/remote/remote_driver.c | 1 + src/remote/remote_protocol.x | 17 +++++++++++- src/remote_protocol-structs | 6 +++++ 12 files changed, 150 insertions(+), 1 deletion(-)
diff --git a/include/libvirt/libvirt-nodedev.h b/include/libvirt/libvirt-nodedev.h index f7ddbfa4ad..de1d5bcbc1 100644 --- a/include/libvirt/libvirt-nodedev.h +++ b/include/libvirt/libvirt-nodedev.h @@ -188,6 +188,24 @@ int virNodeDeviceIsPersistent(virNodeDevicePtr dev);
int virNodeDeviceIsActive(virNodeDevicePtr dev);
+/** + * virNodeDeviceUpdateXMLFlags: + * + * Flags to control options for virNodeDeviceUpdateXML() + * + * Since: 10.1.0 + */ +typedef enum { + VIR_NODE_DEVICE_UPDATE_XML_AFFECT_CURRENT = 0, /* affect live if node device is active, + config if it's not active (Since: 10.1.0) */ + VIR_NODE_DEVICE_UPDATE_XML_AFFECT_LIVE = 1 << 0, /* affect live state of node device only (Since: 10.1.0) */ + VIR_NODE_DEVICE_UPDATE_XML_AFFECT_CONFIG = 1 << 1, /* affect persistent config only (Since: 10.1.0) */ +} virNodeDeviceUpdateXMLFlags; + +int virNodeDeviceUpdateXML(virNodeDevicePtr dev, + const char *xmlDesc, + unsigned int flags); + /** * VIR_NODE_DEVICE_EVENT_CALLBACK: * diff --git a/src/access/viraccessperm.c b/src/access/viraccessperm.c index d4a0c98b9b..702bee761d 100644 --- a/src/access/viraccessperm.c +++ b/src/access/viraccessperm.c @@ -71,6 +71,7 @@ VIR_ENUM_IMPL(virAccessPermNodeDevice, "getattr", "read", "write", "start", "stop", "detach", "delete", + "save", );
VIR_ENUM_IMPL(virAccessPermNWFilter, diff --git a/src/access/viraccessperm.h b/src/access/viraccessperm.h index 2f04459ed9..6cc2140c67 100644 --- a/src/access/viraccessperm.h +++ b/src/access/viraccessperm.h @@ -507,6 +507,12 @@ typedef enum { */ VIR_ACCESS_PERM_NODE_DEVICE_DELETE,
+ /** + * @desc: Save node device + * @message: Saving node device driver requires authorization + */ + VIR_ACCESS_PERM_NODE_DEVICE_SAVE, + VIR_ACCESS_PERM_NODE_DEVICE_LAST } virAccessPermNodeDevice;
diff --git a/src/conf/virnodedeviceobj.c b/src/conf/virnodedeviceobj.c index 31ec4249d8..a8b1b625a7 100644 --- a/src/conf/virnodedeviceobj.c +++ b/src/conf/virnodedeviceobj.c @@ -1137,3 +1137,45 @@ virNodeDeviceObjListFind(virNodeDeviceObjList *devs, virNodeDeviceObjListFindHelper, &data); } + + +/** + * virNodeDeviceObjUpdateModificationImpact: + * @obj: Pointer to node device object + * @flags: flags to update the modification impact on + * + * Resolves virNodeDeviceObjUpdateModificationImpact flags in @flags so that + * they correctly apply to the actual state of @obj. @flags may be modified + * after call to this function. + * + * Returns 0 on success if @flags point to a valid combination for @obj or -1 + * on error. + */ +int +virNodeDeviceObjUpdateModificationImpact(virNodeDeviceObj *obj, + unsigned int *flags) +{ + bool isActive = virNodeDeviceObjIsActive(obj); + + if ((*flags & (VIR_NODE_DEVICE_UPDATE_XML_AFFECT_LIVE | VIR_NODE_DEVICE_UPDATE_XML_AFFECT_CONFIG)) == + VIR_NODE_DEVICE_UPDATE_XML_AFFECT_CURRENT) { + if (isActive) + *flags |= VIR_NODE_DEVICE_UPDATE_XML_AFFECT_LIVE; + else + *flags |= VIR_NODE_DEVICE_UPDATE_XML_AFFECT_CONFIG; + } + + if (!isActive && (*flags & VIR_NODE_DEVICE_UPDATE_XML_AFFECT_LIVE)) { + virReportError(VIR_ERR_OPERATION_INVALID, "%s", + _("mdev is not active")); + return -1;
The error message should be device-type-agnostic rather than mentioning mdev specifically
+ } + + if (!virNodeDeviceObjIsPersistent(obj) && (*flags & VIR_NODE_DEVICE_UPDATE_XML_AFFECT_CONFIG)) { + virReportError(VIR_ERR_OPERATION_INVALID, "%s", + _("transient mdevs do not have any persistent config")); + return -1;
same
+ } + + return 0; +} diff --git a/src/conf/virnodedeviceobj.h b/src/conf/virnodedeviceobj.h index ba2e424998..43889b8dc8 100644 --- a/src/conf/virnodedeviceobj.h +++ b/src/conf/virnodedeviceobj.h @@ -159,3 +159,6 @@ virNodeDeviceObj * virNodeDeviceObjListFind(virNodeDeviceObjList *devs, virNodeDeviceObjListPredicate callback, void *opaque); + +int virNodeDeviceObjUpdateModificationImpact(virNodeDeviceObj *obj, + unsigned int *flags); diff --git a/src/driver-nodedev.h b/src/driver-nodedev.h index 167a8166dd..1c6a731f4e 100644 --- a/src/driver-nodedev.h +++ b/src/driver-nodedev.h @@ -101,6 +101,11 @@ typedef int typedef int (*virDrvNodeDeviceIsActive)(virNodeDevicePtr dev);
+typedef int +(*virDrvNodeDeviceUpdateXML)(virNodeDevicePtr dev, + const char *xmlDesc, + unsigned int flags); + typedef int (*virDrvConnectNodeDeviceEventRegisterAny)(virConnectPtr conn, virNodeDevicePtr dev, @@ -146,4 +151,5 @@ struct _virNodeDeviceDriver { virDrvNodeDeviceGetAutostart nodeDeviceGetAutostart; virDrvNodeDeviceIsPersistent nodeDeviceIsPersistent; virDrvNodeDeviceIsActive nodeDeviceIsActive; + virDrvNodeDeviceUpdateXML nodeDeviceUpdateXML; }; diff --git a/src/libvirt-nodedev.c b/src/libvirt-nodedev.c index c683b2eef9..064ee48650 100644 --- a/src/libvirt-nodedev.c +++ b/src/libvirt-nodedev.c @@ -1174,3 +1174,48 @@ int virNodeDeviceIsActive(virNodeDevicePtr dev) virDispatchError(dev->conn); return -1; } + + +/** + * virNodeDeviceUpdateXML: + * @dev: pointer to the node device object + * @xmlDesc: string containing an XML description of the device to be defined + * @flags: bitwise OR of virNodeDeviceUpdateXMLFlags + * + * Update the definition of an existing node device, either its live + * running configuration, its persistent configuration, or both. + * + * Returns 0 in case of success, -1 in case of error + * + * Since: 10.1.0 + */ +int +virNodeDeviceUpdateXML(virNodeDevicePtr dev, + const char *xmlDesc, + unsigned int flags) +{ + VIR_DEBUG("nodeDevice=%p, xmlDesc=%s, flags=0x%x", + dev, NULLSTR(xmlDesc), flags); + + virResetLastError(); + + virCheckNodeDeviceReturn(dev, -1); + + virCheckReadOnlyGoto(dev->conn->flags, error); + virCheckNonNullArgGoto(xmlDesc, error); + + if (dev->conn->nodeDeviceDriver && + dev->conn->nodeDeviceDriver->nodeDeviceUpdateXML) { + int retval = dev->conn->nodeDeviceDriver->nodeDeviceUpdateXML(dev, xmlDesc, flags); + if (retval < 0) + goto error; + + return 0; + } + + virReportUnsupportedError(); + + error: + virDispatchError(dev->conn); + return -1; +} diff --git a/src/libvirt_private.syms b/src/libvirt_private.syms index c398371734..fae873aba0 100644 --- a/src/libvirt_private.syms +++ b/src/libvirt_private.syms @@ -1373,6 +1373,7 @@ virNodeDeviceObjListRemoveLocked; virNodeDeviceObjSetActive; virNodeDeviceObjSetAutostart; virNodeDeviceObjSetPersistent; +virNodeDeviceObjUpdateModificationImpact;
# conf/virnwfilterbindingdef.h diff --git a/src/libvirt_public.syms b/src/libvirt_public.syms index bd1e916d2a..8b53eeb2ee 100644 --- a/src/libvirt_public.syms +++ b/src/libvirt_public.syms @@ -938,4 +938,9 @@ LIBVIRT_9.7.0 { virNetworkSetMetadata; } LIBVIRT_9.0.0;
+LIBVIRT_10.1.0 { + global: + virNodeDeviceUpdateXML; +} LIBVIRT_9.7.0; + # .... define new API here using predicted next version number .... diff --git a/src/remote/remote_driver.c b/src/remote/remote_driver.c index bedf2cb833..ca7e125141 100644 --- a/src/remote/remote_driver.c +++ b/src/remote/remote_driver.c @@ -7983,6 +7983,7 @@ static virNodeDeviceDriver node_device_driver = { .nodeDeviceSetAutostart = remoteNodeDeviceSetAutostart, /* 7.8.0 */ .nodeDeviceIsPersistent = remoteNodeDeviceIsPersistent, /* 7.8.0 */ .nodeDeviceIsActive = remoteNodeDeviceIsActive, /* 7.8.0 */ + .nodeDeviceUpdateXML = remoteNodeDeviceUpdateXML, /* 10.1.0 */ };
static virNWFilterDriver nwfilter_driver = { diff --git a/src/remote/remote_protocol.x b/src/remote/remote_protocol.x index e295b0acc3..e95f672daf 100644 --- a/src/remote/remote_protocol.x +++ b/src/remote/remote_protocol.x @@ -2237,6 +2237,12 @@ struct remote_node_device_is_active_ret { int active; };
+struct remote_node_device_update_xml_args { + remote_nonnull_string name; + remote_nonnull_string xml_desc; + unsigned int flags; +}; +
/* * Events Register/Deregister: @@ -7021,5 +7027,14 @@ enum remote_procedure { * @generate: both * @acl: none */ - REMOTE_PROC_NETWORK_EVENT_CALLBACK_METADATA_CHANGE = 446 + REMOTE_PROC_NETWORK_EVENT_CALLBACK_METADATA_CHANGE = 446, + + /** + * @generate: both + * @priority: high + * @acl: node_device:write + * @acl: node_device:save:!VIR_NODE_DEVICE_UPDATE_XML_AFFECT_CONFIG|VIR_NODE_DEVICE_UPDATE_XML_AFFECT_LIVE + * @acl: node_device:save:VIR_NODE_DEVICE_UPDATE_XML_AFFECT_CONFIG + */ + REMOTE_PROC_NODE_DEVICE_UPDATE_XML = 447 }; diff --git a/src/remote_protocol-structs b/src/remote_protocol-structs index 924ca41825..9a1b881caf 100644 --- a/src/remote_protocol-structs +++ b/src/remote_protocol-structs @@ -1673,6 +1673,11 @@ struct remote_node_device_is_active_args { struct remote_node_device_is_active_ret { int active; }; +struct remote_node_device_update_xml_args { + remote_nonnull_string name; + remote_nonnull_string xml_desc; + u_int flags; +}; struct remote_connect_domain_event_register_ret { int cb_registered; }; @@ -3743,4 +3748,5 @@ enum remote_procedure { REMOTE_PROC_NETWORK_SET_METADATA = 444, REMOTE_PROC_NETWORK_GET_METADATA = 445, REMOTE_PROC_NETWORK_EVENT_CALLBACK_METADATA_CHANGE = 446, + REMOTE_PROC_NODE_DEVICE_UPDATE_XML = 447, };

On 2/9/24 23:13, Jonathon Jongsma wrote:
On 2/7/24 7:39 AM, Boris Fiuczynski wrote:
This public API is implemented for almost all other objects that have a concept of persistent definition and activatability. Node devices (mdevs) that can be defined and inactive, it will be useful to be able to update defined and active node devices as well.
It looks like my previous comment on this patch might not have been sent to the list, so I'll repeat it here:
In the commit summary, you talk about virNodeDeviceUpdate(), however, in the code you actually name the function virNodeDeviceUpdateXML(). There are no other public objects that provide a $(OBJECT)UpdateXML() function. The only other object that has a similar API is virNetworkUpdate(). That doesn't seem to match your claim that "this public API is implemented for almost all other objects...". What API were you referring to exactly? I think for API consistency, perhaps the Update() name is better than UpdateXML() to match virNetworkUpdate()? Regardless, the commit message should match the function name.
Sorry, I forgot to change the method name in the summary as before I named it without the suffix XML. I will change the name back to match the networking which is the only method allowing to update live/active objects. I was trying to say that updating and/or modifying is possible on almost all other objects which have implemented persistent and active configurations. Most use *Define.. to update but this is an update of the persistent config as you already noted. As I wrote above already virNetworkUpdate is the only method which also supports update of the active config.
Tangentially related:
Almost all of the other API objects can update their persistent definition from an XML file by calling the $(OBJECT)DefineXML command to replace the existing definition. The Node device API does not yet support doing that, maybe we should strive for a bit more API consistency here and implement that as well.
Clearly the DefineXML() API will only operate on a persistent definition, so if we want to provide a way to update a live device, we'll need a new API, which is presumably where this one comes in...
Correct. I will take a look at how to align virNodeDeviceDefineXML
Signed-off-by: Boris Fiuczynski <fiuczy@linux.ibm.com> --- include/libvirt/libvirt-nodedev.h | 18 +++++++++++++ src/access/viraccessperm.c | 1 + src/access/viraccessperm.h | 6 +++++ src/conf/virnodedeviceobj.c | 42 +++++++++++++++++++++++++++++ src/conf/virnodedeviceobj.h | 3 +++ src/driver-nodedev.h | 6 +++++ src/libvirt-nodedev.c | 45 +++++++++++++++++++++++++++++++ src/libvirt_private.syms | 1 + src/libvirt_public.syms | 5 ++++ src/remote/remote_driver.c | 1 + src/remote/remote_protocol.x | 17 +++++++++++- src/remote_protocol-structs | 6 +++++ 12 files changed, 150 insertions(+), 1 deletion(-)
diff --git a/include/libvirt/libvirt-nodedev.h b/include/libvirt/libvirt-nodedev.h index f7ddbfa4ad..de1d5bcbc1 100644 --- a/include/libvirt/libvirt-nodedev.h +++ b/include/libvirt/libvirt-nodedev.h @@ -188,6 +188,24 @@ int virNodeDeviceIsPersistent(virNodeDevicePtr dev); int virNodeDeviceIsActive(virNodeDevicePtr dev); +/** + * virNodeDeviceUpdateXMLFlags: + * + * Flags to control options for virNodeDeviceUpdateXML() + * + * Since: 10.1.0 + */ +typedef enum { + VIR_NODE_DEVICE_UPDATE_XML_AFFECT_CURRENT = 0, /* affect live if node device is active, + config if it's not active (Since: 10.1.0) */ + VIR_NODE_DEVICE_UPDATE_XML_AFFECT_LIVE = 1 << 0, /* affect live state of node device only (Since: 10.1.0) */ + VIR_NODE_DEVICE_UPDATE_XML_AFFECT_CONFIG = 1 << 1, /* affect persistent config only (Since: 10.1.0) */ +} virNodeDeviceUpdateXMLFlags; + +int virNodeDeviceUpdateXML(virNodeDevicePtr dev, + const char *xmlDesc, + unsigned int flags); + /** * VIR_NODE_DEVICE_EVENT_CALLBACK: * diff --git a/src/access/viraccessperm.c b/src/access/viraccessperm.c index d4a0c98b9b..702bee761d 100644 --- a/src/access/viraccessperm.c +++ b/src/access/viraccessperm.c @@ -71,6 +71,7 @@ VIR_ENUM_IMPL(virAccessPermNodeDevice, "getattr", "read", "write", "start", "stop", "detach", "delete", + "save", ); VIR_ENUM_IMPL(virAccessPermNWFilter, diff --git a/src/access/viraccessperm.h b/src/access/viraccessperm.h index 2f04459ed9..6cc2140c67 100644 --- a/src/access/viraccessperm.h +++ b/src/access/viraccessperm.h @@ -507,6 +507,12 @@ typedef enum { */ VIR_ACCESS_PERM_NODE_DEVICE_DELETE, + /** + * @desc: Save node device + * @message: Saving node device driver requires authorization + */ + VIR_ACCESS_PERM_NODE_DEVICE_SAVE, + VIR_ACCESS_PERM_NODE_DEVICE_LAST } virAccessPermNodeDevice; diff --git a/src/conf/virnodedeviceobj.c b/src/conf/virnodedeviceobj.c index 31ec4249d8..a8b1b625a7 100644 --- a/src/conf/virnodedeviceobj.c +++ b/src/conf/virnodedeviceobj.c @@ -1137,3 +1137,45 @@ virNodeDeviceObjListFind(virNodeDeviceObjList *devs, virNodeDeviceObjListFindHelper, &data); } + + +/** + * virNodeDeviceObjUpdateModificationImpact: + * @obj: Pointer to node device object + * @flags: flags to update the modification impact on + * + * Resolves virNodeDeviceObjUpdateModificationImpact flags in @flags so that + * they correctly apply to the actual state of @obj. @flags may be modified + * after call to this function. + * + * Returns 0 on success if @flags point to a valid combination for @obj or -1 + * on error. + */ +int +virNodeDeviceObjUpdateModificationImpact(virNodeDeviceObj *obj, + unsigned int *flags) +{ + bool isActive = virNodeDeviceObjIsActive(obj); + + if ((*flags & (VIR_NODE_DEVICE_UPDATE_XML_AFFECT_LIVE | VIR_NODE_DEVICE_UPDATE_XML_AFFECT_CONFIG)) == + VIR_NODE_DEVICE_UPDATE_XML_AFFECT_CURRENT) { + if (isActive) + *flags |= VIR_NODE_DEVICE_UPDATE_XML_AFFECT_LIVE; + else + *flags |= VIR_NODE_DEVICE_UPDATE_XML_AFFECT_CONFIG; + } + + if (!isActive && (*flags & VIR_NODE_DEVICE_UPDATE_XML_AFFECT_LIVE)) { + virReportError(VIR_ERR_OPERATION_INVALID, "%s", + _("mdev is not active")); + return -1;
The error message should be device-type-agnostic rather than mentioning mdev specifically
Changed "mdev" into "node device"
+ } + + if (!virNodeDeviceObjIsPersistent(obj) && (*flags & VIR_NODE_DEVICE_UPDATE_XML_AFFECT_CONFIG)) { + virReportError(VIR_ERR_OPERATION_INVALID, "%s", + _("transient mdevs do not have any persistent config")); + return -1;
same
Changed.
+ } + + return 0; +} diff --git a/src/conf/virnodedeviceobj.h b/src/conf/virnodedeviceobj.h index ba2e424998..43889b8dc8 100644 --- a/src/conf/virnodedeviceobj.h +++ b/src/conf/virnodedeviceobj.h @@ -159,3 +159,6 @@ virNodeDeviceObj * virNodeDeviceObjListFind(virNodeDeviceObjList *devs, virNodeDeviceObjListPredicate callback, void *opaque); + +int virNodeDeviceObjUpdateModificationImpact(virNodeDeviceObj *obj, + unsigned int *flags); diff --git a/src/driver-nodedev.h b/src/driver-nodedev.h index 167a8166dd..1c6a731f4e 100644 --- a/src/driver-nodedev.h +++ b/src/driver-nodedev.h @@ -101,6 +101,11 @@ typedef int typedef int (*virDrvNodeDeviceIsActive)(virNodeDevicePtr dev); +typedef int +(*virDrvNodeDeviceUpdateXML)(virNodeDevicePtr dev, + const char *xmlDesc, + unsigned int flags); + typedef int (*virDrvConnectNodeDeviceEventRegisterAny)(virConnectPtr conn, virNodeDevicePtr dev, @@ -146,4 +151,5 @@ struct _virNodeDeviceDriver { virDrvNodeDeviceGetAutostart nodeDeviceGetAutostart; virDrvNodeDeviceIsPersistent nodeDeviceIsPersistent; virDrvNodeDeviceIsActive nodeDeviceIsActive; + virDrvNodeDeviceUpdateXML nodeDeviceUpdateXML; }; diff --git a/src/libvirt-nodedev.c b/src/libvirt-nodedev.c index c683b2eef9..064ee48650 100644 --- a/src/libvirt-nodedev.c +++ b/src/libvirt-nodedev.c @@ -1174,3 +1174,48 @@ int virNodeDeviceIsActive(virNodeDevicePtr dev) virDispatchError(dev->conn); return -1; } + + +/** + * virNodeDeviceUpdateXML: + * @dev: pointer to the node device object + * @xmlDesc: string containing an XML description of the device to be defined + * @flags: bitwise OR of virNodeDeviceUpdateXMLFlags + * + * Update the definition of an existing node device, either its live + * running configuration, its persistent configuration, or both. + * + * Returns 0 in case of success, -1 in case of error + * + * Since: 10.1.0 + */ +int +virNodeDeviceUpdateXML(virNodeDevicePtr dev, + const char *xmlDesc, + unsigned int flags) +{ + VIR_DEBUG("nodeDevice=%p, xmlDesc=%s, flags=0x%x", + dev, NULLSTR(xmlDesc), flags); + + virResetLastError(); + + virCheckNodeDeviceReturn(dev, -1); + + virCheckReadOnlyGoto(dev->conn->flags, error); + virCheckNonNullArgGoto(xmlDesc, error); + + if (dev->conn->nodeDeviceDriver && + dev->conn->nodeDeviceDriver->nodeDeviceUpdateXML) { + int retval = dev->conn->nodeDeviceDriver->nodeDeviceUpdateXML(dev, xmlDesc, flags); + if (retval < 0) + goto error; + + return 0; + } + + virReportUnsupportedError(); + + error: + virDispatchError(dev->conn); + return -1; +} diff --git a/src/libvirt_private.syms b/src/libvirt_private.syms index c398371734..fae873aba0 100644 --- a/src/libvirt_private.syms +++ b/src/libvirt_private.syms @@ -1373,6 +1373,7 @@ virNodeDeviceObjListRemoveLocked; virNodeDeviceObjSetActive; virNodeDeviceObjSetAutostart; virNodeDeviceObjSetPersistent; +virNodeDeviceObjUpdateModificationImpact; # conf/virnwfilterbindingdef.h diff --git a/src/libvirt_public.syms b/src/libvirt_public.syms index bd1e916d2a..8b53eeb2ee 100644 --- a/src/libvirt_public.syms +++ b/src/libvirt_public.syms @@ -938,4 +938,9 @@ LIBVIRT_9.7.0 { virNetworkSetMetadata; } LIBVIRT_9.0.0; +LIBVIRT_10.1.0 { + global: + virNodeDeviceUpdateXML; +} LIBVIRT_9.7.0; + # .... define new API here using predicted next version number .... diff --git a/src/remote/remote_driver.c b/src/remote/remote_driver.c index bedf2cb833..ca7e125141 100644 --- a/src/remote/remote_driver.c +++ b/src/remote/remote_driver.c @@ -7983,6 +7983,7 @@ static virNodeDeviceDriver node_device_driver = { .nodeDeviceSetAutostart = remoteNodeDeviceSetAutostart, /* 7.8.0 */ .nodeDeviceIsPersistent = remoteNodeDeviceIsPersistent, /* 7.8.0 */ .nodeDeviceIsActive = remoteNodeDeviceIsActive, /* 7.8.0 */ + .nodeDeviceUpdateXML = remoteNodeDeviceUpdateXML, /* 10.1.0 */ }; static virNWFilterDriver nwfilter_driver = { diff --git a/src/remote/remote_protocol.x b/src/remote/remote_protocol.x index e295b0acc3..e95f672daf 100644 --- a/src/remote/remote_protocol.x +++ b/src/remote/remote_protocol.x @@ -2237,6 +2237,12 @@ struct remote_node_device_is_active_ret { int active; }; +struct remote_node_device_update_xml_args { + remote_nonnull_string name; + remote_nonnull_string xml_desc; + unsigned int flags; +}; + /* * Events Register/Deregister: @@ -7021,5 +7027,14 @@ enum remote_procedure { * @generate: both * @acl: none */ - REMOTE_PROC_NETWORK_EVENT_CALLBACK_METADATA_CHANGE = 446 + REMOTE_PROC_NETWORK_EVENT_CALLBACK_METADATA_CHANGE = 446, + + /** + * @generate: both + * @priority: high + * @acl: node_device:write + * @acl: node_device:save:!VIR_NODE_DEVICE_UPDATE_XML_AFFECT_CONFIG|VIR_NODE_DEVICE_UPDATE_XML_AFFECT_LIVE + * @acl: node_device:save:VIR_NODE_DEVICE_UPDATE_XML_AFFECT_CONFIG + */ + REMOTE_PROC_NODE_DEVICE_UPDATE_XML = 447 }; diff --git a/src/remote_protocol-structs b/src/remote_protocol-structs index 924ca41825..9a1b881caf 100644 --- a/src/remote_protocol-structs +++ b/src/remote_protocol-structs @@ -1673,6 +1673,11 @@ struct remote_node_device_is_active_args { struct remote_node_device_is_active_ret { int active; }; +struct remote_node_device_update_xml_args { + remote_nonnull_string name; + remote_nonnull_string xml_desc; + u_int flags; +}; struct remote_connect_domain_event_register_ret { int cb_registered; }; @@ -3743,4 +3748,5 @@ enum remote_procedure { REMOTE_PROC_NETWORK_SET_METADATA = 444, REMOTE_PROC_NETWORK_GET_METADATA = 445, REMOTE_PROC_NETWORK_EVENT_CALLBACK_METADATA_CHANGE = 446, + REMOTE_PROC_NODE_DEVICE_UPDATE_XML = 447, };
Devel mailing list -- devel@lists.libvirt.org To unsubscribe send an email to devel-leave@lists.libvirt.org
-- Mit freundlichen Grüßen/Kind regards Boris Fiuczynski IBM Deutschland Research & Development GmbH Vorsitzender des Aufsichtsrats: Wolfgang Wendt Geschäftsführung: David Faller Sitz der Gesellschaft: Böblingen Registergericht: Amtsgericht Stuttgart, HRB 243294

On 2/13/24 14:43, Boris Fiuczynski wrote:
Tangentially related:
Almost all of the other API objects can update their persistent definition from an XML file by calling the $(OBJECT)DefineXML command to replace the existing definition. The Node device API does not yet support doing that, maybe we should strive for a bit more API consistency here and implement that as well.
Clearly the DefineXML() API will only operate on a persistent definition, so if we want to provide a way to update a live device, we'll need a new API, which is presumably where this one comes in...
Correct. I will take a look at how to align virNodeDeviceDefineXML
The current libvirt code does not prevent to define an existing mdev and does call mdevctl which reports that mdevctl cowardly refuses to overwrite the existing config as an internal error. # virsh nodedev-define vfio-ccw-mdev-c60c_new-attr-set1.xml error: Failed to define node device from 'vfio-ccw-mdev-c60c_new-attr-set1.xml' error: internal error: Unable to define mediated device: Error: Cowardly refusing to overwrite existing config for 0.0.0008/c60cc60c-c60c-c60c-c60c-c60cc60cc60c Instead of handling this in libvirt by detecting on define if a persistent config already exists and instead of calling 'mdevctl define' using 'mdevctl modify' for the update should mdevctl become brave by removing its refusal? -- Mit freundlichen Grüßen/Kind regards Boris Fiuczynski IBM Deutschland Research & Development GmbH Vorsitzender des Aufsichtsrats: Wolfgang Wendt Geschäftsführung: David Faller Sitz der Gesellschaft: Böblingen Registergericht: Amtsgericht Stuttgart, HRB 243294

On 2/14/24 6:59 AM, Boris Fiuczynski wrote:
On 2/13/24 14:43, Boris Fiuczynski wrote:
Tangentially related:
Almost all of the other API objects can update their persistent definition from an XML file by calling the $(OBJECT)DefineXML command to replace the existing definition. The Node device API does not yet support doing that, maybe we should strive for a bit more API consistency here and implement that as well.
Clearly the DefineXML() API will only operate on a persistent definition, so if we want to provide a way to update a live device, we'll need a new API, which is presumably where this one comes in...
Correct. I will take a look at how to align virNodeDeviceDefineXML
The current libvirt code does not prevent to define an existing mdev and does call mdevctl which reports that mdevctl cowardly refuses to overwrite the existing config as an internal error.
# virsh nodedev-define vfio-ccw-mdev-c60c_new-attr-set1.xml error: Failed to define node device from 'vfio-ccw-mdev-c60c_new-attr-set1.xml' error: internal error: Unable to define mediated device: Error: Cowardly refusing to overwrite existing config for 0.0.0008/c60cc60c-c60c-c60c-c60c-c60cc60cc60c
Instead of handling this in libvirt by detecting on define if a persistent config already exists and instead of calling 'mdevctl define' using 'mdevctl modify' for the update should mdevctl become brave by removing its refusal?
I don't think it would be a good idea to change the behavior of mdevctl at this point. But even if we did change mdevctl behavior, we'd still have to deal with older versions of mdevctl so we'd have to handle it in libvirt anyway. Jonathon

On 2/14/24 19:00, Jonathon Jongsma wrote:
On 2/14/24 6:59 AM, Boris Fiuczynski wrote:
On 2/13/24 14:43, Boris Fiuczynski wrote:
Tangentially related:
Almost all of the other API objects can update their persistent definition from an XML file by calling the $(OBJECT)DefineXML command to replace the existing definition. The Node device API does not yet support doing that, maybe we should strive for a bit more API consistency here and implement that as well.
Clearly the DefineXML() API will only operate on a persistent definition, so if we want to provide a way to update a live device, we'll need a new API, which is presumably where this one comes in...
Correct. I will take a look at how to align virNodeDeviceDefineXML
The current libvirt code does not prevent to define an existing mdev and does call mdevctl which reports that mdevctl cowardly refuses to overwrite the existing config as an internal error.
# virsh nodedev-define vfio-ccw-mdev-c60c_new-attr-set1.xml error: Failed to define node device from 'vfio-ccw-mdev-c60c_new-attr-set1.xml' error: internal error: Unable to define mediated device: Error: Cowardly refusing to overwrite existing config for 0.0.0008/c60cc60c-c60c-c60c-c60c-c60cc60cc60c
Instead of handling this in libvirt by detecting on define if a persistent config already exists and instead of calling 'mdevctl define' using 'mdevctl modify' for the update should mdevctl become brave by removing its refusal?
I don't think it would be a good idea to change the behavior of mdevctl at this point. But even if we did change mdevctl behavior, we'd still have to deal with older versions of mdevctl so we'd have to handle it in libvirt anyway.
Jonathon
What would have to change in libvirt to handle the current versions not allowing to overwrite the config? The error above would remain for older medevctl versions. For versions which support overwrite the above error would disappear and the nodedev-define would succeed. There might be some changes required in libvirt to emit a different libvirt life cycle event for overwrites vs new defines. In case a event change is required in libvirt that would be missing if an older libvirt version is using a new mdevctl version.
_______________________________________________ Devel mailing list -- devel@lists.libvirt.org To unsubscribe send an email to devel-leave@lists.libvirt.org
-- Mit freundlichen Grüßen/Kind regards Boris Fiuczynski IBM Deutschland Research & Development GmbH Vorsitzender des Aufsichtsrats: Wolfgang Wendt Geschäftsführung: David Faller Sitz der Gesellschaft: Böblingen Registergericht: Amtsgericht Stuttgart, HRB 243294

On 2/15/24 3:29 AM, Boris Fiuczynski wrote:
On 2/14/24 19:00, Jonathon Jongsma wrote:
On 2/14/24 6:59 AM, Boris Fiuczynski wrote:
On 2/13/24 14:43, Boris Fiuczynski wrote:
Tangentially related:
Almost all of the other API objects can update their persistent definition from an XML file by calling the $(OBJECT)DefineXML command to replace the existing definition. The Node device API does not yet support doing that, maybe we should strive for a bit more API consistency here and implement that as well.
Clearly the DefineXML() API will only operate on a persistent definition, so if we want to provide a way to update a live device, we'll need a new API, which is presumably where this one comes in...
Correct. I will take a look at how to align virNodeDeviceDefineXML
The current libvirt code does not prevent to define an existing mdev and does call mdevctl which reports that mdevctl cowardly refuses to overwrite the existing config as an internal error.
# virsh nodedev-define vfio-ccw-mdev-c60c_new-attr-set1.xml error: Failed to define node device from 'vfio-ccw-mdev-c60c_new-attr-set1.xml' error: internal error: Unable to define mediated device: Error: Cowardly refusing to overwrite existing config for 0.0.0008/c60cc60c-c60c-c60c-c60c-c60cc60cc60c
Instead of handling this in libvirt by detecting on define if a persistent config already exists and instead of calling 'mdevctl define' using 'mdevctl modify' for the update should mdevctl become brave by removing its refusal?
I don't think it would be a good idea to change the behavior of mdevctl at this point. But even if we did change mdevctl behavior, we'd still have to deal with older versions of mdevctl so we'd have to handle it in libvirt anyway.
Jonathon
What would have to change in libvirt to handle the current versions not allowing to overwrite the config? The error above would remain for older medevctl versions.
You're right. If you don't care that older mdevctl versions don't work, you wouldn't need to do anything. But we should be able to very easily support the existing versions right now simply by checking whether the device is already defined and using 'modify' instead. And as I said, I don't currently intend to change mdevctl define behavior. Jonathon

Implement the API functions in the node device driver by using mdevctl modify with the options defined and live. Instead of increasing the minimum mdevctl version to 1.3.0 in spec file to ensure support exists in mdevctl the support is dynamically checked before using mdevctl. Signed-off-by: Boris Fiuczynski <fiuczy@linux.ibm.com> --- NEWS.rst | 7 + libvirt.spec.in | 1 + src/node_device/node_device_driver.c | 256 +++++++++++++++++- src/node_device/node_device_driver.h | 11 + src/node_device/node_device_udev.c | 1 + ...60c_c60c_c60c_c60c_c60cc60cc60c_update.xml | 16 ++ tests/nodedevmdevctldata/mdevctl-modify.argv | 25 ++ tests/nodedevmdevctldata/mdevctl-modify.json | 1 + tests/nodedevmdevctltest.c | 90 +++++- 9 files changed, 405 insertions(+), 3 deletions(-) create mode 100644 tests/nodedevmdevctldata/mdev_c60cc60c_c60c_c60c_c60c_c60cc60cc60c_update.xml create mode 100644 tests/nodedevmdevctldata/mdevctl-modify.argv create mode 100644 tests/nodedevmdevctldata/mdevctl-modify.json diff --git a/NEWS.rst b/NEWS.rst index e2796fd8b2..aa2fee3533 100644 --- a/NEWS.rst +++ b/NEWS.rst @@ -17,6 +17,13 @@ v10.1.0 (unreleased) * **New features** + * nodedev: Support updating mdevs + + The node device driver has been extended to allow updating mediated node + devices. Options are available to target the update against the persisted, + active or both configurations of an mediated device. + **Note:** The support is only available with at least mdevctl v1.3.0 installed. + * qemu: Support clusters in CPU topology It is now possible to configure the guest CPU topology to use clusters. diff --git a/libvirt.spec.in b/libvirt.spec.in index 8413e3c19a..a8dff4a32e 100644 --- a/libvirt.spec.in +++ b/libvirt.spec.in @@ -613,6 +613,7 @@ Requires: libvirt-libs = %{version}-%{release} # needed for device enumeration Requires: systemd >= 185 # For managing persistent mediated devices +# Note: for nodedev-update support at least mdevctl v1.3.0 is required Requires: mdevctl # for modprobe of pci devices Requires: module-init-tools diff --git a/src/node_device/node_device_driver.c b/src/node_device/node_device_driver.c index baff49a0ae..5bece1a30a 100644 --- a/src/node_device/node_device_driver.c +++ b/src/node_device/node_device_driver.c @@ -54,7 +54,7 @@ virNodeDeviceDriverState *driver; VIR_ENUM_IMPL(virMdevctlCommand, MDEVCTL_CMD_LAST, - "start", "stop", "define", "undefine", "create" + "start", "stop", "define", "undefine", "create", "modify" ); @@ -754,6 +754,7 @@ nodeDeviceGetMdevctlCommand(virNodeDeviceDef *def, case MDEVCTL_CMD_START: case MDEVCTL_CMD_DEFINE: case MDEVCTL_CMD_UNDEFINE: + case MDEVCTL_CMD_MODIFY: cmd = virCommandNewArgList(MDEVCTL, subcommand, NULL); break; case MDEVCTL_CMD_LAST: @@ -767,6 +768,7 @@ nodeDeviceGetMdevctlCommand(virNodeDeviceDef *def, switch (cmd_type) { case MDEVCTL_CMD_CREATE: case MDEVCTL_CMD_DEFINE: + case MDEVCTL_CMD_MODIFY: if (!def->caps->data.mdev.parent_addr) { virReportError(VIR_ERR_INTERNAL_ERROR, _("unable to find parent device '%1$s'"), def->parent); @@ -783,7 +785,8 @@ nodeDeviceGetMdevctlCommand(virNodeDeviceDef *def, virCommandAddArgPair(cmd, "--jsonfile", "/dev/stdin"); virCommandSetInputBuffer(cmd, inbuf); - virCommandSetOutputBuffer(cmd, outbuf); + if (outbuf) + virCommandSetOutputBuffer(cmd, outbuf); break; case MDEVCTL_CMD_UNDEFINE: @@ -868,6 +871,100 @@ virMdevctlDefine(virNodeDeviceDef *def, char **uuid) } +/* gets a virCommand object that executes a mdevctl command to modify a + * a device to an updated version + */ +virCommand* +nodeDeviceGetMdevctlModifyCommand(virNodeDeviceDef *def, + bool defined, + bool live, + char **errmsg) +{ + virCommand *cmd = nodeDeviceGetMdevctlCommand(def, + MDEVCTL_CMD_MODIFY, + NULL, errmsg); + + if (!cmd) + return NULL; + + if (defined) + virCommandAddArg(cmd, "--defined"); + + if (live) + virCommandAddArg(cmd, "--live"); + + return cmd; +} + + +/* checks if mdevctl supports on the command modify the options live, defined + * and jsonfile + */ +static int +nodeDeviceGetMdevctlModifySupportCheck(void) +{ + int status; + g_autoptr(virCommand) cmd = NULL; + const char *subcommand = virMdevctlCommandTypeToString(MDEVCTL_CMD_MODIFY); + + cmd = virCommandNewArgList(MDEVCTL, + subcommand, + "--defined", + "--live", + "--jsonfile", + "b", + "--help", + NULL); + + if (!cmd) + return -1; + + if (virCommandRun(cmd, &status) < 0) + return -1; + + if (status != 0) // update is unsupported + return -1; + + return 0; +} + + +static int +virMdevctlModify(virNodeDeviceDef *def, + bool defined, + bool live) +{ + int status; + g_autofree char *errmsg = NULL; + g_autoptr(virCommand) cmd = nodeDeviceGetMdevctlModifyCommand(def, + defined, + live, + &errmsg); + + if (!cmd) + return -1; + + if (nodeDeviceGetMdevctlModifySupportCheck() < 0) { + VIR_WARN("Installed mdevctl version does not support modify with options jsonfile, defined and live"); + virReportError(VIR_ERR_OPERATION_UNSUPPORTED, "%s", + _("Unable to modify mediated device: modify unsupported")); + return -1; + } + + if (virCommandRun(cmd, &status) < 0) + return -1; + + if (status != 0) { + virReportError(VIR_ERR_INTERNAL_ERROR, + _("Unable to modify mediated device: %1$s"), + MDEVCTL_ERROR(errmsg)); + return -1; + } + + return 0; +} + + static virNodeDevicePtr nodeDeviceCreateXMLMdev(virConnectPtr conn, virNodeDeviceDef *def) @@ -2106,3 +2203,158 @@ nodeDeviceIsActive(virNodeDevice *device) virNodeDeviceObjEndAPI(&obj); return ret; } + + +static int +nodeDeviceDefCloneMdevConfigs(virNodeDeviceDef *def_upd) +{ + virNodeDevCapsDef *caps = NULL; + virNodeDevCapMdev *cap_mdev_upd = NULL; + virMediatedDeviceConfig *src_mdev_config = NULL; + virMediatedDeviceConfig *dst_mdev_config = NULL; + + for (caps = def_upd->caps; caps != NULL; caps = caps->next) { + if (caps->data.type == VIR_NODE_DEV_CAP_MDEV) { + cap_mdev_upd = &caps->data.mdev; + } + } + if (!cap_mdev_upd) + return -1; + + src_mdev_config = &cap_mdev_upd->defined_config; + dst_mdev_config = &cap_mdev_upd->active_config; + + if (STRNEQ_NULLABLE(dst_mdev_config->type, src_mdev_config->type)) { + g_free(dst_mdev_config->type); + dst_mdev_config->type = g_strdup(src_mdev_config->type); + } + + virMediatedDeviceAttrsCopy(dst_mdev_config, src_mdev_config); + + return 0; +} + + +static int +nodeDeviceDefCompareMdevs(virNodeDeviceDef *def_cur, + virNodeDeviceDef *def_upd, + bool live) +{ + virNodeDevCapsDef *caps = NULL; + virNodeDevCapMdev *cap_mdev_cur = NULL; + virNodeDevCapMdev *cap_mdev_upd = NULL; + + for (caps = def_cur->caps; caps != NULL; caps = caps->next) { + if (caps->data.type == VIR_NODE_DEV_CAP_MDEV) { + cap_mdev_cur = &caps->data.mdev; + } + } + if (!cap_mdev_cur) + return -1; + + for (caps = def_upd->caps; caps != NULL; caps = caps->next) { + if (caps->data.type == VIR_NODE_DEV_CAP_MDEV) { + cap_mdev_upd = &caps->data.mdev; + } + } + if (!cap_mdev_upd) + return -1; + + if (STRNEQ_NULLABLE(cap_mdev_cur->uuid, cap_mdev_upd->uuid)) { + virReportError(VIR_ERR_CONFIG_UNSUPPORTED, + _("Cannot update device '%1$s, uuid mismatch (current uuid '%2$s')"), + def_cur->name, + cap_mdev_cur->uuid); + return -1; + } + // for a live update the types of the active configs must match! + if (live && + STRNEQ_NULLABLE(cap_mdev_cur->active_config.type, cap_mdev_upd->active_config.type)) { + virReportError(VIR_ERR_CONFIG_UNSUPPORTED, + _("Cannot update device '%1$s', type mismatch (current type '%2$s')"), + def_cur->name, + cap_mdev_cur->active_config.type); + return -1; + } + if (STRNEQ_NULLABLE(cap_mdev_cur->parent_addr, cap_mdev_upd->parent_addr)) { + virReportError(VIR_ERR_CONFIG_UNSUPPORTED, + _("Cannot update device '%1$s', parent address mismatch (current parent address '%2$s')"), + def_cur->name, + cap_mdev_cur->parent_addr); + return -1; + } + + return 0; +} + + +int +nodeDeviceUpdateXML(virNodeDevice *device, + const char *xmlDesc, + unsigned int flags) +{ + int ret = -1; + virNodeDeviceObj *obj = NULL; + virNodeDeviceDef *def_cur; + g_autoptr(virNodeDeviceDef) def_upd = NULL; + const char *virt_type = NULL; + bool updated = false; + + virCheckFlags(VIR_NODE_DEVICE_UPDATE_XML_AFFECT_LIVE | + VIR_NODE_DEVICE_UPDATE_XML_AFFECT_CONFIG, -1); + + if (nodeDeviceInitWait() < 0) + return -1; + + if (!(obj = nodeDeviceObjFindByName(device->name))) + return -1; + def_cur = virNodeDeviceObjGetDef(obj); + + virt_type = virConnectGetType(device->conn); + + if (virNodeDeviceUpdateXMLEnsureACL(device->conn, def_cur, flags) < 0) + goto cleanup; + + if (!(def_upd = virNodeDeviceDefParse(xmlDesc, NULL, EXISTING_DEVICE, virt_type, + &driver->parserCallbacks, NULL, true))) + goto cleanup; + + if (nodeDeviceHasCapability(def_cur, VIR_NODE_DEV_CAP_MDEV) && + nodeDeviceHasCapability(def_upd, VIR_NODE_DEV_CAP_MDEV)) { + // Checks flags are valid for the state and sets flags for current if flags not set + if (virNodeDeviceObjUpdateModificationImpact(obj, &flags) < 0) + goto cleanup; + + // Before live update clone defined to active config + if (flags & VIR_NODE_DEVICE_UPDATE_XML_AFFECT_LIVE && + nodeDeviceDefCloneMdevConfigs(def_upd) < 0) + goto cleanup; + + // Compare def_cur and def_upd for compatibleness e.g. parent, type and uuid + if (nodeDeviceDefCompareMdevs(def_cur, def_upd, + (flags & VIR_NODE_DEVICE_UPDATE_XML_AFFECT_LIVE)) < 0) + goto cleanup; + + // Update now + VIR_DEBUG("Update node device '%s' with mdevctl", def_cur->name); + if (virMdevctlModify(def_upd, + (flags & VIR_NODE_DEVICE_UPDATE_XML_AFFECT_CONFIG), + (flags & VIR_NODE_DEVICE_UPDATE_XML_AFFECT_LIVE)) < 0) { + goto cleanup; + }; + // Detect updates and also trigger events + updated = true; + } else { + virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s", + _("Unsupported device type")); + goto cleanup; + } + + ret = 0; + cleanup: + virNodeDeviceObjEndAPI(&obj); + if (updated) + nodeDeviceUpdateMediatedDevices(); + + return ret; +} diff --git a/src/node_device/node_device_driver.h b/src/node_device/node_device_driver.h index 4dce7e6f17..5751625eda 100644 --- a/src/node_device/node_device_driver.h +++ b/src/node_device/node_device_driver.h @@ -39,6 +39,7 @@ typedef enum { * separation makes our code more readable in terms of knowing when we're * starting a defined device and when we're creating a transient one */ MDEVCTL_CMD_CREATE, + MDEVCTL_CMD_MODIFY, MDEVCTL_CMD_LAST, } virMdevctlCommand; @@ -186,3 +187,13 @@ virCommand* nodeDeviceGetMdevctlSetAutostartCommand(virNodeDeviceDef *def, bool autostart, char **errmsg); + +virCommand* +nodeDeviceGetMdevctlModifyCommand(virNodeDeviceDef *def, + bool defined, + bool live, + char **errmsg); +int +nodeDeviceUpdateXML(virNodeDevice *dev, + const char *xmlDesc, + unsigned int flags); diff --git a/src/node_device/node_device_udev.c b/src/node_device/node_device_udev.c index 57368a96c3..4cd9d285fa 100644 --- a/src/node_device/node_device_udev.c +++ b/src/node_device/node_device_udev.c @@ -2403,6 +2403,7 @@ static virNodeDeviceDriver udevNodeDeviceDriver = { .nodeDeviceGetAutostart = nodeDeviceGetAutostart, /* 7.8.0 */ .nodeDeviceIsPersistent = nodeDeviceIsPersistent, /* 7.8.0 */ .nodeDeviceIsActive = nodeDeviceIsActive, /* 7.8.0 */ + .nodeDeviceUpdateXML = nodeDeviceUpdateXML, /* 10.1.0 */ }; diff --git a/tests/nodedevmdevctldata/mdev_c60cc60c_c60c_c60c_c60c_c60cc60cc60c_update.xml b/tests/nodedevmdevctldata/mdev_c60cc60c_c60c_c60c_c60c_c60cc60cc60c_update.xml new file mode 100644 index 0000000000..17e3611bf4 --- /dev/null +++ b/tests/nodedevmdevctldata/mdev_c60cc60c_c60c_c60c_c60c_c60cc60cc60c_update.xml @@ -0,0 +1,16 @@ +<device> + <name>mdev_c60cc60c_c60c_c60c_c60c_c60cc60cc60c_0_0_0052</name> + <path>/sys/devices/css0/0.0.0052/c60cc60c-c60c-c60c-c60c-c60cc60cc60c</path> + <parent>css_0_0_0052</parent> + <driver> + <name>vfio_ccw_mdev</name> + </driver> + <capability type='mdev'> + <type id='vfio_ccw-io'/> + <uuid>c60cc60c-c60c-c60c-c60c-c60cc60cc60c</uuid> + <parent_addr>0.0.0052</parent_addr> + <attr name='add_attr_1' value='val1'/> + <attr name='add_attr_2' value='val2'/> + <iommuGroup number='4'/> + </capability> +</device> diff --git a/tests/nodedevmdevctldata/mdevctl-modify.argv b/tests/nodedevmdevctldata/mdevctl-modify.argv new file mode 100644 index 0000000000..341c5e7fd0 --- /dev/null +++ b/tests/nodedevmdevctldata/mdevctl-modify.argv @@ -0,0 +1,25 @@ +mdevctl \ +modify \ +--parent=0.0.0052 \ +--jsonfile=/dev/stdin \ +--uuid=c60cc60c-c60c-c60c-c60c-c60cc60cc60c \ +--defined +mdevctl \ +modify \ +--parent=0.0.0052 \ +--jsonfile=/dev/stdin \ +--uuid=c60cc60c-c60c-c60c-c60c-c60cc60cc60c \ +--live +mdevctl \ +modify \ +--parent=0.0.0052 \ +--jsonfile=/dev/stdin \ +--uuid=c60cc60c-c60c-c60c-c60c-c60cc60cc60c \ +--live +mdevctl \ +modify \ +--parent=0.0.0052 \ +--jsonfile=/dev/stdin \ +--uuid=c60cc60c-c60c-c60c-c60c-c60cc60cc60c \ +--defined \ +--live diff --git a/tests/nodedevmdevctldata/mdevctl-modify.json b/tests/nodedevmdevctldata/mdevctl-modify.json new file mode 100644 index 0000000000..7ae8c73ce3 --- /dev/null +++ b/tests/nodedevmdevctldata/mdevctl-modify.json @@ -0,0 +1 @@ +{"mdev_type":"vfio_ccw-io","start":"manual"}{"mdev_type":"vfio_ccw-io","start":"manual","attrs":[{"add_attr_1":"val1"},{"add_attr_2":"val2"}]}{"mdev_type":"vfio_ccw-io","start":"manual"}{"mdev_type":"vfio_ccw-io","start":"manual","attrs":[{"add_attr_1":"val1"},{"add_attr_2":"val2"}]} diff --git a/tests/nodedevmdevctltest.c b/tests/nodedevmdevctltest.c index f49d668461..11273e3049 100644 --- a/tests/nodedevmdevctltest.c +++ b/tests/nodedevmdevctltest.c @@ -33,7 +33,10 @@ testCommandDryRunCallback(const char *const*args G_GNUC_UNUSED, { char **stdinbuf = opaque; - *stdinbuf = g_strdup(input); + if (*stdinbuf) + *stdinbuf = g_strconcat(*stdinbuf, input, NULL); + else + *stdinbuf = g_strdup(input); } typedef virCommand * (*MdevctlCmdFunc)(virNodeDeviceDef *, char **, char **); @@ -63,6 +66,7 @@ testMdevctlCmd(virMdevctlCommand cmd_type, case MDEVCTL_CMD_START: case MDEVCTL_CMD_STOP: case MDEVCTL_CMD_UNDEFINE: + case MDEVCTL_CMD_MODIFY: create = EXISTING_DEVICE; break; case MDEVCTL_CMD_LAST: @@ -173,6 +177,85 @@ testMdevctlAutostart(const void *data G_GNUC_UNUSED) return ret; } + +static int +testMdevctlModify(const void *data G_GNUC_UNUSED) +{ + g_autoptr(virNodeDeviceDef) def = NULL; + g_autoptr(virNodeDeviceDef) def_update = NULL; + virBuffer buf = VIR_BUFFER_INITIALIZER; + const char *actualCmdline = NULL; + int ret = -1; + g_autoptr(virCommand) definedcmd = NULL; + g_autoptr(virCommand) livecmd = NULL; + g_autoptr(virCommand) bothcmd = NULL; + g_autofree char *errmsg = NULL; + g_autofree char *stdinbuf = NULL; + g_autofree char *mdevxml = + g_strdup_printf("%s/nodedevschemadata/mdev_c60cc60c_c60c_c60c_c60c_c60cc60cc60c.xml", + abs_srcdir); + g_autofree char *mdevxml_update = + g_strdup_printf("%s/nodedevmdevctldata/mdev_c60cc60c_c60c_c60c_c60c_c60cc60cc60c_update.xml", + abs_srcdir); + /* just concatenate both calls into the same output files */ + g_autofree char *cmdlinefile = + g_strdup_printf("%s/nodedevmdevctldata/mdevctl-modify.argv", + abs_srcdir); + g_autofree char *jsonfile = + g_strdup_printf("%s/nodedevmdevctldata/mdevctl-modify.json", + abs_srcdir); + g_autoptr(virCommandDryRunToken) dryRunToken = virCommandDryRunTokenNew(); + + if (!(def = virNodeDeviceDefParse(NULL, mdevxml, CREATE_DEVICE, VIRT_TYPE, + &parser_callbacks, NULL, false))) + return -1; + + virCommandSetDryRun(dryRunToken, &buf, true, true, testCommandDryRunCallback, &stdinbuf); + + if (!(definedcmd = nodeDeviceGetMdevctlModifyCommand(def, true, false, &errmsg))) + goto cleanup; + + if (virCommandRun(definedcmd, NULL) < 0) + goto cleanup; + + if (!(def_update = virNodeDeviceDefParse(NULL, mdevxml_update, EXISTING_DEVICE, VIRT_TYPE, + &parser_callbacks, NULL, false))) + goto cleanup; + + if (!(livecmd = nodeDeviceGetMdevctlModifyCommand(def_update, false, true, &errmsg))) + goto cleanup; + + if (virCommandRun(livecmd, NULL) < 0) + goto cleanup; + + if (!(livecmd = nodeDeviceGetMdevctlModifyCommand(def, false, true, &errmsg))) + goto cleanup; + + if (virCommandRun(livecmd, NULL) < 0) + goto cleanup; + + if (!(bothcmd = nodeDeviceGetMdevctlModifyCommand(def_update, true, true, &errmsg))) + goto cleanup; + + if (virCommandRun(bothcmd, NULL) < 0) + goto cleanup; + + if (!(actualCmdline = virBufferCurrentContent(&buf))) + goto cleanup; + + if (virTestCompareToFileFull(actualCmdline, cmdlinefile, false) < 0) + goto cleanup; + + if (virTestCompareToFile(stdinbuf, jsonfile) < 0) + goto cleanup; + + ret = 0; + + cleanup: + virBufferFreeAndReset(&buf); + return ret; +} + static int testMdevctlListDefined(const void *data G_GNUC_UNUSED) { @@ -457,6 +540,9 @@ mymain(void) #define DO_TEST_AUTOSTART() \ DO_TEST_FULL("autostart mdevs", testMdevctlAutostart, NULL) +#define DO_TEST_MODIFY() \ + DO_TEST_FULL("modify mdevs", testMdevctlModify, NULL) + #define DO_TEST_PARSE_JSON(filename) \ DO_TEST_FULL("parse mdevctl json " filename, testMdevctlParse, filename) @@ -485,6 +571,8 @@ mymain(void) DO_TEST_AUTOSTART(); + DO_TEST_MODIFY(); + done: nodedevTestDriverFree(driver); -- 2.42.0

On 2/7/24 7:39 AM, Boris Fiuczynski wrote:
Implement the API functions in the node device driver by using mdevctl modify with the options defined and live. Instead of increasing the minimum mdevctl version to 1.3.0 in spec file to ensure support exists in mdevctl the support is dynamically checked before using mdevctl.
Signed-off-by: Boris Fiuczynski <fiuczy@linux.ibm.com> --- NEWS.rst | 7 + libvirt.spec.in | 1 + src/node_device/node_device_driver.c | 256 +++++++++++++++++- src/node_device/node_device_driver.h | 11 + src/node_device/node_device_udev.c | 1 + ...60c_c60c_c60c_c60c_c60cc60cc60c_update.xml | 16 ++ tests/nodedevmdevctldata/mdevctl-modify.argv | 25 ++ tests/nodedevmdevctldata/mdevctl-modify.json | 1 + tests/nodedevmdevctltest.c | 90 +++++- 9 files changed, 405 insertions(+), 3 deletions(-) create mode 100644 tests/nodedevmdevctldata/mdev_c60cc60c_c60c_c60c_c60c_c60cc60cc60c_update.xml create mode 100644 tests/nodedevmdevctldata/mdevctl-modify.argv create mode 100644 tests/nodedevmdevctldata/mdevctl-modify.json
diff --git a/NEWS.rst b/NEWS.rst index e2796fd8b2..aa2fee3533 100644 --- a/NEWS.rst +++ b/NEWS.rst @@ -17,6 +17,13 @@ v10.1.0 (unreleased)
* **New features**
+ * nodedev: Support updating mdevs + + The node device driver has been extended to allow updating mediated node + devices. Options are available to target the update against the persisted, + active or both configurations of an mediated device. + **Note:** The support is only available with at least mdevctl v1.3.0 installed. + * qemu: Support clusters in CPU topology
It is now possible to configure the guest CPU topology to use clusters. diff --git a/libvirt.spec.in b/libvirt.spec.in index 8413e3c19a..a8dff4a32e 100644 --- a/libvirt.spec.in +++ b/libvirt.spec.in @@ -613,6 +613,7 @@ Requires: libvirt-libs = %{version}-%{release} # needed for device enumeration Requires: systemd >= 185 # For managing persistent mediated devices +# Note: for nodedev-update support at least mdevctl v1.3.0 is required Requires: mdevctl # for modprobe of pci devices Requires: module-init-tools diff --git a/src/node_device/node_device_driver.c b/src/node_device/node_device_driver.c index baff49a0ae..5bece1a30a 100644 --- a/src/node_device/node_device_driver.c +++ b/src/node_device/node_device_driver.c @@ -54,7 +54,7 @@ virNodeDeviceDriverState *driver;
VIR_ENUM_IMPL(virMdevctlCommand, MDEVCTL_CMD_LAST, - "start", "stop", "define", "undefine", "create" + "start", "stop", "define", "undefine", "create", "modify" );
@@ -754,6 +754,7 @@ nodeDeviceGetMdevctlCommand(virNodeDeviceDef *def, case MDEVCTL_CMD_START: case MDEVCTL_CMD_DEFINE: case MDEVCTL_CMD_UNDEFINE: + case MDEVCTL_CMD_MODIFY: cmd = virCommandNewArgList(MDEVCTL, subcommand, NULL); break; case MDEVCTL_CMD_LAST: @@ -767,6 +768,7 @@ nodeDeviceGetMdevctlCommand(virNodeDeviceDef *def, switch (cmd_type) { case MDEVCTL_CMD_CREATE: case MDEVCTL_CMD_DEFINE: + case MDEVCTL_CMD_MODIFY: if (!def->caps->data.mdev.parent_addr) { virReportError(VIR_ERR_INTERNAL_ERROR, _("unable to find parent device '%1$s'"), def->parent); @@ -783,7 +785,8 @@ nodeDeviceGetMdevctlCommand(virNodeDeviceDef *def, virCommandAddArgPair(cmd, "--jsonfile", "/dev/stdin");
virCommandSetInputBuffer(cmd, inbuf); - virCommandSetOutputBuffer(cmd, outbuf); + if (outbuf) + virCommandSetOutputBuffer(cmd, outbuf); break;
case MDEVCTL_CMD_UNDEFINE: @@ -868,6 +871,100 @@ virMdevctlDefine(virNodeDeviceDef *def, char **uuid) }
+/* gets a virCommand object that executes a mdevctl command to modify a + * a device to an updated version + */ +virCommand* +nodeDeviceGetMdevctlModifyCommand(virNodeDeviceDef *def, + bool defined, + bool live, + char **errmsg) +{ + virCommand *cmd = nodeDeviceGetMdevctlCommand(def, + MDEVCTL_CMD_MODIFY, + NULL, errmsg); + + if (!cmd) + return NULL; + + if (defined) + virCommandAddArg(cmd, "--defined"); + + if (live) + virCommandAddArg(cmd, "--live"); + + return cmd; +} + + +/* checks if mdevctl supports on the command modify the options live, defined + * and jsonfile + */ +static int +nodeDeviceGetMdevctlModifySupportCheck(void) +{ + int status; + g_autoptr(virCommand) cmd = NULL; + const char *subcommand = virMdevctlCommandTypeToString(MDEVCTL_CMD_MODIFY); + + cmd = virCommandNewArgList(MDEVCTL, + subcommand, + "--defined", + "--live", + "--jsonfile", + "b", + "--help", + NULL); + + if (!cmd) + return -1; + + if (virCommandRun(cmd, &status) < 0) + return -1; + + if (status != 0) // update is unsupported + return -1; + + return 0; +} + + +static int +virMdevctlModify(virNodeDeviceDef *def, + bool defined, + bool live) +{ + int status; + g_autofree char *errmsg = NULL; + g_autoptr(virCommand) cmd = nodeDeviceGetMdevctlModifyCommand(def, + defined, + live, + &errmsg); + + if (!cmd) + return -1; + + if (nodeDeviceGetMdevctlModifySupportCheck() < 0) { + VIR_WARN("Installed mdevctl version does not support modify with options jsonfile, defined and live"); + virReportError(VIR_ERR_OPERATION_UNSUPPORTED, "%s", + _("Unable to modify mediated device: modify unsupported")); + return -1; + } + + if (virCommandRun(cmd, &status) < 0) + return -1; + + if (status != 0) { + virReportError(VIR_ERR_INTERNAL_ERROR, + _("Unable to modify mediated device: %1$s"), + MDEVCTL_ERROR(errmsg)); + return -1; + } + + return 0; +} + + static virNodeDevicePtr nodeDeviceCreateXMLMdev(virConnectPtr conn, virNodeDeviceDef *def) @@ -2106,3 +2203,158 @@ nodeDeviceIsActive(virNodeDevice *device) virNodeDeviceObjEndAPI(&obj); return ret; } + + +static int +nodeDeviceDefCloneMdevConfigs(virNodeDeviceDef *def_upd) +{ + virNodeDevCapsDef *caps = NULL; + virNodeDevCapMdev *cap_mdev_upd = NULL; + virMediatedDeviceConfig *src_mdev_config = NULL; + virMediatedDeviceConfig *dst_mdev_config = NULL; + + for (caps = def_upd->caps; caps != NULL; caps = caps->next) { + if (caps->data.type == VIR_NODE_DEV_CAP_MDEV) { + cap_mdev_upd = &caps->data.mdev; + } + } + if (!cap_mdev_upd) + return -1; + + src_mdev_config = &cap_mdev_upd->defined_config; + dst_mdev_config = &cap_mdev_upd->active_config; + + if (STRNEQ_NULLABLE(dst_mdev_config->type, src_mdev_config->type)) { + g_free(dst_mdev_config->type); + dst_mdev_config->type = g_strdup(src_mdev_config->type); + } + + virMediatedDeviceAttrsCopy(dst_mdev_config, src_mdev_config); + + return 0; +} + + +static int +nodeDeviceDefCompareMdevs(virNodeDeviceDef *def_cur, + virNodeDeviceDef *def_upd, + bool live) +{ + virNodeDevCapsDef *caps = NULL; + virNodeDevCapMdev *cap_mdev_cur = NULL; + virNodeDevCapMdev *cap_mdev_upd = NULL; + + for (caps = def_cur->caps; caps != NULL; caps = caps->next) { + if (caps->data.type == VIR_NODE_DEV_CAP_MDEV) { + cap_mdev_cur = &caps->data.mdev; + } + } + if (!cap_mdev_cur) + return -1; + + for (caps = def_upd->caps; caps != NULL; caps = caps->next) { + if (caps->data.type == VIR_NODE_DEV_CAP_MDEV) { + cap_mdev_upd = &caps->data.mdev; + } + } + if (!cap_mdev_upd) + return -1; + + if (STRNEQ_NULLABLE(cap_mdev_cur->uuid, cap_mdev_upd->uuid)) { + virReportError(VIR_ERR_CONFIG_UNSUPPORTED, + _("Cannot update device '%1$s, uuid mismatch (current uuid '%2$s')"), + def_cur->name, + cap_mdev_cur->uuid); + return -1; + } + // for a live update the types of the active configs must match! + if (live && + STRNEQ_NULLABLE(cap_mdev_cur->active_config.type, cap_mdev_upd->active_config.type)) { + virReportError(VIR_ERR_CONFIG_UNSUPPORTED, + _("Cannot update device '%1$s', type mismatch (current type '%2$s')"), + def_cur->name, + cap_mdev_cur->active_config.type); + return -1; + } + if (STRNEQ_NULLABLE(cap_mdev_cur->parent_addr, cap_mdev_upd->parent_addr)) { + virReportError(VIR_ERR_CONFIG_UNSUPPORTED, + _("Cannot update device '%1$s', parent address mismatch (current parent address '%2$s')"), + def_cur->name, + cap_mdev_cur->parent_addr); + return -1; + } + + return 0; +} + + +int +nodeDeviceUpdateXML(virNodeDevice *device, + const char *xmlDesc, + unsigned int flags) +{ + int ret = -1; + virNodeDeviceObj *obj = NULL; + virNodeDeviceDef *def_cur; + g_autoptr(virNodeDeviceDef) def_upd = NULL > + const char *virt_type = NULL; + bool updated = false; + + virCheckFlags(VIR_NODE_DEVICE_UPDATE_XML_AFFECT_LIVE | + VIR_NODE_DEVICE_UPDATE_XML_AFFECT_CONFIG, -1); + + if (nodeDeviceInitWait() < 0) + return -1; + + if (!(obj = nodeDeviceObjFindByName(device->name))) + return -1; + def_cur = virNodeDeviceObjGetDef(obj); + + virt_type = virConnectGetType(device->conn); + + if (virNodeDeviceUpdateXMLEnsureACL(device->conn, def_cur, flags) < 0) + goto cleanup; + + if (!(def_upd = virNodeDeviceDefParse(xmlDesc, NULL, EXISTING_DEVICE, virt_type, + &driver->parserCallbacks, NULL, true))) + goto cleanup; + + if (nodeDeviceHasCapability(def_cur, VIR_NODE_DEV_CAP_MDEV) && + nodeDeviceHasCapability(def_upd, VIR_NODE_DEV_CAP_MDEV)) { + // Checks flags are valid for the state and sets flags for current if flags not set + if (virNodeDeviceObjUpdateModificationImpact(obj, &flags) < 0) + goto cleanup; + + // Before live update clone defined to active config + if (flags & VIR_NODE_DEVICE_UPDATE_XML_AFFECT_LIVE && + nodeDeviceDefCloneMdevConfigs(def_upd) < 0) + goto cleanup;
It's not clear to me why this is necessary. As far as I can tell, only the defined_config is used to generate the JSON to send to mdevctl. And this def is freed at the end of the function and then we re-query mdevctl to update our device list.
+ + // Compare def_cur and def_upd for compatibleness e.g. parent, type and uuid
compatibleness -> compatibility
+ if (nodeDeviceDefCompareMdevs(def_cur, def_upd, + (flags & VIR_NODE_DEVICE_UPDATE_XML_AFFECT_LIVE)) < 0) + goto cleanup; + + // Update now + VIR_DEBUG("Update node device '%s' with mdevctl", def_cur->name); + if (virMdevctlModify(def_upd, + (flags & VIR_NODE_DEVICE_UPDATE_XML_AFFECT_CONFIG), + (flags & VIR_NODE_DEVICE_UPDATE_XML_AFFECT_LIVE)) < 0) { + goto cleanup; + }; + // Detect updates and also trigger events + updated = true; + } else { + virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s", + _("Unsupported device type")); + goto cleanup; + } + + ret = 0; + cleanup: + virNodeDeviceObjEndAPI(&obj); + if (updated) + nodeDeviceUpdateMediatedDevices(); + + return ret; +} diff --git a/src/node_device/node_device_driver.h b/src/node_device/node_device_driver.h index 4dce7e6f17..5751625eda 100644 --- a/src/node_device/node_device_driver.h +++ b/src/node_device/node_device_driver.h @@ -39,6 +39,7 @@ typedef enum { * separation makes our code more readable in terms of knowing when we're * starting a defined device and when we're creating a transient one */ MDEVCTL_CMD_CREATE, + MDEVCTL_CMD_MODIFY,
MDEVCTL_CMD_LAST, } virMdevctlCommand; @@ -186,3 +187,13 @@ virCommand* nodeDeviceGetMdevctlSetAutostartCommand(virNodeDeviceDef *def, bool autostart, char **errmsg); + +virCommand* +nodeDeviceGetMdevctlModifyCommand(virNodeDeviceDef *def, + bool defined, + bool live, + char **errmsg); +int +nodeDeviceUpdateXML(virNodeDevice *dev, + const char *xmlDesc, + unsigned int flags); diff --git a/src/node_device/node_device_udev.c b/src/node_device/node_device_udev.c index 57368a96c3..4cd9d285fa 100644 --- a/src/node_device/node_device_udev.c +++ b/src/node_device/node_device_udev.c @@ -2403,6 +2403,7 @@ static virNodeDeviceDriver udevNodeDeviceDriver = { .nodeDeviceGetAutostart = nodeDeviceGetAutostart, /* 7.8.0 */ .nodeDeviceIsPersistent = nodeDeviceIsPersistent, /* 7.8.0 */ .nodeDeviceIsActive = nodeDeviceIsActive, /* 7.8.0 */ + .nodeDeviceUpdateXML = nodeDeviceUpdateXML, /* 10.1.0 */ };
diff --git a/tests/nodedevmdevctldata/mdev_c60cc60c_c60c_c60c_c60c_c60cc60cc60c_update.xml b/tests/nodedevmdevctldata/mdev_c60cc60c_c60c_c60c_c60c_c60cc60cc60c_update.xml new file mode 100644 index 0000000000..17e3611bf4 --- /dev/null +++ b/tests/nodedevmdevctldata/mdev_c60cc60c_c60c_c60c_c60c_c60cc60cc60c_update.xml @@ -0,0 +1,16 @@ +<device> + <name>mdev_c60cc60c_c60c_c60c_c60c_c60cc60cc60c_0_0_0052</name> + <path>/sys/devices/css0/0.0.0052/c60cc60c-c60c-c60c-c60c-c60cc60cc60c</path> + <parent>css_0_0_0052</parent> + <driver> + <name>vfio_ccw_mdev</name> + </driver> + <capability type='mdev'> + <type id='vfio_ccw-io'/> + <uuid>c60cc60c-c60c-c60c-c60c-c60cc60cc60c</uuid> + <parent_addr>0.0.0052</parent_addr> + <attr name='add_attr_1' value='val1'/> + <attr name='add_attr_2' value='val2'/> + <iommuGroup number='4'/> + </capability> +</device> diff --git a/tests/nodedevmdevctldata/mdevctl-modify.argv b/tests/nodedevmdevctldata/mdevctl-modify.argv new file mode 100644 index 0000000000..341c5e7fd0 --- /dev/null +++ b/tests/nodedevmdevctldata/mdevctl-modify.argv @@ -0,0 +1,25 @@ +mdevctl \ +modify \ +--parent=0.0.0052 \ +--jsonfile=/dev/stdin \ +--uuid=c60cc60c-c60c-c60c-c60c-c60cc60cc60c \ +--defined +mdevctl \ +modify \ +--parent=0.0.0052 \ +--jsonfile=/dev/stdin \ +--uuid=c60cc60c-c60c-c60c-c60c-c60cc60cc60c \ +--live +mdevctl \ +modify \ +--parent=0.0.0052 \ +--jsonfile=/dev/stdin \ +--uuid=c60cc60c-c60c-c60c-c60c-c60cc60cc60c \ +--live +mdevctl \ +modify \ +--parent=0.0.0052 \ +--jsonfile=/dev/stdin \ +--uuid=c60cc60c-c60c-c60c-c60c-c60cc60cc60c \ +--defined \ +--live diff --git a/tests/nodedevmdevctldata/mdevctl-modify.json b/tests/nodedevmdevctldata/mdevctl-modify.json new file mode 100644 index 0000000000..7ae8c73ce3 --- /dev/null +++ b/tests/nodedevmdevctldata/mdevctl-modify.json @@ -0,0 +1 @@ +{"mdev_type":"vfio_ccw-io","start":"manual"}{"mdev_type":"vfio_ccw-io","start":"manual","attrs":[{"add_attr_1":"val1"},{"add_attr_2":"val2"}]}{"mdev_type":"vfio_ccw-io","start":"manual"}{"mdev_type":"vfio_ccw-io","start":"manual","attrs":[{"add_attr_1":"val1"},{"add_attr_2":"val2"}]} diff --git a/tests/nodedevmdevctltest.c b/tests/nodedevmdevctltest.c index f49d668461..11273e3049 100644 --- a/tests/nodedevmdevctltest.c +++ b/tests/nodedevmdevctltest.c @@ -33,7 +33,10 @@ testCommandDryRunCallback(const char *const*args G_GNUC_UNUSED, { char **stdinbuf = opaque;
- *stdinbuf = g_strdup(input); + if (*stdinbuf) + *stdinbuf = g_strconcat(*stdinbuf, input, NULL);
I think g_strjoin() would be nicer so we can add a newline separator between invocations.
+ else + *stdinbuf = g_strdup(input); }
typedef virCommand * (*MdevctlCmdFunc)(virNodeDeviceDef *, char **, char **); @@ -63,6 +66,7 @@ testMdevctlCmd(virMdevctlCommand cmd_type, case MDEVCTL_CMD_START: case MDEVCTL_CMD_STOP: case MDEVCTL_CMD_UNDEFINE: + case MDEVCTL_CMD_MODIFY: create = EXISTING_DEVICE; break; case MDEVCTL_CMD_LAST: @@ -173,6 +177,85 @@ testMdevctlAutostart(const void *data G_GNUC_UNUSED) return ret; }
+ +static int +testMdevctlModify(const void *data G_GNUC_UNUSED) +{ + g_autoptr(virNodeDeviceDef) def = NULL; + g_autoptr(virNodeDeviceDef) def_update = NULL; + virBuffer buf = VIR_BUFFER_INITIALIZER; + const char *actualCmdline = NULL; + int ret = -1; + g_autoptr(virCommand) definedcmd = NULL; + g_autoptr(virCommand) livecmd = NULL; + g_autoptr(virCommand) bothcmd = NULL; + g_autofree char *errmsg = NULL; + g_autofree char *stdinbuf = NULL; + g_autofree char *mdevxml = + g_strdup_printf("%s/nodedevschemadata/mdev_c60cc60c_c60c_c60c_c60c_c60cc60cc60c.xml", + abs_srcdir); + g_autofree char *mdevxml_update = + g_strdup_printf("%s/nodedevmdevctldata/mdev_c60cc60c_c60c_c60c_c60c_c60cc60cc60c_update.xml", + abs_srcdir); + /* just concatenate both calls into the same output files */ + g_autofree char *cmdlinefile = + g_strdup_printf("%s/nodedevmdevctldata/mdevctl-modify.argv", + abs_srcdir); + g_autofree char *jsonfile = + g_strdup_printf("%s/nodedevmdevctldata/mdevctl-modify.json", + abs_srcdir); + g_autoptr(virCommandDryRunToken) dryRunToken = virCommandDryRunTokenNew(); + + if (!(def = virNodeDeviceDefParse(NULL, mdevxml, CREATE_DEVICE, VIRT_TYPE, + &parser_callbacks, NULL, false))) + return -1; + + virCommandSetDryRun(dryRunToken, &buf, true, true, testCommandDryRunCallback, &stdinbuf); + + if (!(definedcmd = nodeDeviceGetMdevctlModifyCommand(def, true, false, &errmsg))) + goto cleanup; + + if (virCommandRun(definedcmd, NULL) < 0) + goto cleanup; + + if (!(def_update = virNodeDeviceDefParse(NULL, mdevxml_update, EXISTING_DEVICE, VIRT_TYPE, + &parser_callbacks, NULL, false))) + goto cleanup; + + if (!(livecmd = nodeDeviceGetMdevctlModifyCommand(def_update, false, true, &errmsg))) + goto cleanup; + + if (virCommandRun(livecmd, NULL) < 0) + goto cleanup; + + if (!(livecmd = nodeDeviceGetMdevctlModifyCommand(def, false, true, &errmsg))) + goto cleanup; + + if (virCommandRun(livecmd, NULL) < 0) + goto cleanup; + + if (!(bothcmd = nodeDeviceGetMdevctlModifyCommand(def_update, true, true, &errmsg))) + goto cleanup; + + if (virCommandRun(bothcmd, NULL) < 0) + goto cleanup; + + if (!(actualCmdline = virBufferCurrentContent(&buf))) + goto cleanup; + + if (virTestCompareToFileFull(actualCmdline, cmdlinefile, false) < 0) + goto cleanup; + + if (virTestCompareToFile(stdinbuf, jsonfile) < 0) + goto cleanup; + + ret = 0; + + cleanup: + virBufferFreeAndReset(&buf); + return ret; +} + static int testMdevctlListDefined(const void *data G_GNUC_UNUSED) { @@ -457,6 +540,9 @@ mymain(void) #define DO_TEST_AUTOSTART() \ DO_TEST_FULL("autostart mdevs", testMdevctlAutostart, NULL)
+#define DO_TEST_MODIFY() \ + DO_TEST_FULL("modify mdevs", testMdevctlModify, NULL) + #define DO_TEST_PARSE_JSON(filename) \ DO_TEST_FULL("parse mdevctl json " filename, testMdevctlParse, filename)
@@ -485,6 +571,8 @@ mymain(void)
DO_TEST_AUTOSTART();
+ DO_TEST_MODIFY(); + done: nodedevTestDriverFree(driver);
Reviewed-by: Jonathon Jongsma <jjongsma@redhat.com>

On 2/9/24 23:45, Jonathon Jongsma wrote:
On 2/7/24 7:39 AM, Boris Fiuczynski wrote:
Implement the API functions in the node device driver by using mdevctl modify with the options defined and live. Instead of increasing the minimum mdevctl version to 1.3.0 in spec file to ensure support exists in mdevctl the support is dynamically checked before using mdevctl.
Signed-off-by: Boris Fiuczynski <fiuczy@linux.ibm.com> --- NEWS.rst | 7 + libvirt.spec.in | 1 + src/node_device/node_device_driver.c | 256 +++++++++++++++++- src/node_device/node_device_driver.h | 11 + src/node_device/node_device_udev.c | 1 + ...60c_c60c_c60c_c60c_c60cc60cc60c_update.xml | 16 ++ tests/nodedevmdevctldata/mdevctl-modify.argv | 25 ++ tests/nodedevmdevctldata/mdevctl-modify.json | 1 + tests/nodedevmdevctltest.c | 90 +++++- 9 files changed, 405 insertions(+), 3 deletions(-) create mode 100644 tests/nodedevmdevctldata/mdev_c60cc60c_c60c_c60c_c60c_c60cc60cc60c_update.xml create mode 100644 tests/nodedevmdevctldata/mdevctl-modify.argv create mode 100644 tests/nodedevmdevctldata/mdevctl-modify.json
diff --git a/NEWS.rst b/NEWS.rst index e2796fd8b2..aa2fee3533 100644 --- a/NEWS.rst +++ b/NEWS.rst @@ -17,6 +17,13 @@ v10.1.0 (unreleased) * **New features** + * nodedev: Support updating mdevs + + The node device driver has been extended to allow updating mediated node + devices. Options are available to target the update against the persisted, + active or both configurations of an mediated device. + **Note:** The support is only available with at least mdevctl v1.3.0 installed. + * qemu: Support clusters in CPU topology It is now possible to configure the guest CPU topology to use clusters. diff --git a/libvirt.spec.in b/libvirt.spec.in index 8413e3c19a..a8dff4a32e 100644 --- a/libvirt.spec.in +++ b/libvirt.spec.in @@ -613,6 +613,7 @@ Requires: libvirt-libs = %{version}-%{release} # needed for device enumeration Requires: systemd >= 185 # For managing persistent mediated devices +# Note: for nodedev-update support at least mdevctl v1.3.0 is required Requires: mdevctl # for modprobe of pci devices Requires: module-init-tools diff --git a/src/node_device/node_device_driver.c b/src/node_device/node_device_driver.c index baff49a0ae..5bece1a30a 100644 --- a/src/node_device/node_device_driver.c +++ b/src/node_device/node_device_driver.c @@ -54,7 +54,7 @@ virNodeDeviceDriverState *driver; VIR_ENUM_IMPL(virMdevctlCommand, MDEVCTL_CMD_LAST, - "start", "stop", "define", "undefine", "create" + "start", "stop", "define", "undefine", "create", "modify" ); @@ -754,6 +754,7 @@ nodeDeviceGetMdevctlCommand(virNodeDeviceDef *def, case MDEVCTL_CMD_START: case MDEVCTL_CMD_DEFINE: case MDEVCTL_CMD_UNDEFINE: + case MDEVCTL_CMD_MODIFY: cmd = virCommandNewArgList(MDEVCTL, subcommand, NULL); break; case MDEVCTL_CMD_LAST: @@ -767,6 +768,7 @@ nodeDeviceGetMdevctlCommand(virNodeDeviceDef *def, switch (cmd_type) { case MDEVCTL_CMD_CREATE: case MDEVCTL_CMD_DEFINE: + case MDEVCTL_CMD_MODIFY: if (!def->caps->data.mdev.parent_addr) { virReportError(VIR_ERR_INTERNAL_ERROR, _("unable to find parent device '%1$s'"), def->parent); @@ -783,7 +785,8 @@ nodeDeviceGetMdevctlCommand(virNodeDeviceDef *def, virCommandAddArgPair(cmd, "--jsonfile", "/dev/stdin"); virCommandSetInputBuffer(cmd, inbuf); - virCommandSetOutputBuffer(cmd, outbuf); + if (outbuf) + virCommandSetOutputBuffer(cmd, outbuf); break; case MDEVCTL_CMD_UNDEFINE: @@ -868,6 +871,100 @@ virMdevctlDefine(virNodeDeviceDef *def, char **uuid) } +/* gets a virCommand object that executes a mdevctl command to modify a + * a device to an updated version + */ +virCommand* +nodeDeviceGetMdevctlModifyCommand(virNodeDeviceDef *def, + bool defined, + bool live, + char **errmsg) +{ + virCommand *cmd = nodeDeviceGetMdevctlCommand(def, + MDEVCTL_CMD_MODIFY, + NULL, errmsg); + + if (!cmd) + return NULL; + + if (defined) + virCommandAddArg(cmd, "--defined"); + + if (live) + virCommandAddArg(cmd, "--live"); + + return cmd; +} + + +/* checks if mdevctl supports on the command modify the options live, defined + * and jsonfile + */ +static int +nodeDeviceGetMdevctlModifySupportCheck(void) +{ + int status; + g_autoptr(virCommand) cmd = NULL; + const char *subcommand = virMdevctlCommandTypeToString(MDEVCTL_CMD_MODIFY); + + cmd = virCommandNewArgList(MDEVCTL, + subcommand, + "--defined", + "--live", + "--jsonfile", + "b", + "--help", + NULL); + + if (!cmd) + return -1; + + if (virCommandRun(cmd, &status) < 0) + return -1; + + if (status != 0) // update is unsupported + return -1; + + return 0; +} + + +static int +virMdevctlModify(virNodeDeviceDef *def, + bool defined, + bool live) +{ + int status; + g_autofree char *errmsg = NULL; + g_autoptr(virCommand) cmd = nodeDeviceGetMdevctlModifyCommand(def, + defined, + live, + &errmsg); + + if (!cmd) + return -1; + + if (nodeDeviceGetMdevctlModifySupportCheck() < 0) { + VIR_WARN("Installed mdevctl version does not support modify with options jsonfile, defined and live"); + virReportError(VIR_ERR_OPERATION_UNSUPPORTED, "%s", + _("Unable to modify mediated device: modify unsupported")); + return -1; + } + + if (virCommandRun(cmd, &status) < 0) + return -1; + + if (status != 0) { + virReportError(VIR_ERR_INTERNAL_ERROR, + _("Unable to modify mediated device: %1$s"), + MDEVCTL_ERROR(errmsg)); + return -1; + } + + return 0; +} + + static virNodeDevicePtr nodeDeviceCreateXMLMdev(virConnectPtr conn, virNodeDeviceDef *def) @@ -2106,3 +2203,158 @@ nodeDeviceIsActive(virNodeDevice *device) virNodeDeviceObjEndAPI(&obj); return ret; } + + +static int +nodeDeviceDefCloneMdevConfigs(virNodeDeviceDef *def_upd) +{ + virNodeDevCapsDef *caps = NULL; + virNodeDevCapMdev *cap_mdev_upd = NULL; + virMediatedDeviceConfig *src_mdev_config = NULL; + virMediatedDeviceConfig *dst_mdev_config = NULL; + + for (caps = def_upd->caps; caps != NULL; caps = caps->next) { + if (caps->data.type == VIR_NODE_DEV_CAP_MDEV) { + cap_mdev_upd = &caps->data.mdev; + } + } + if (!cap_mdev_upd) + return -1; + + src_mdev_config = &cap_mdev_upd->defined_config; + dst_mdev_config = &cap_mdev_upd->active_config; + + if (STRNEQ_NULLABLE(dst_mdev_config->type, src_mdev_config->type)) { + g_free(dst_mdev_config->type); + dst_mdev_config->type = g_strdup(src_mdev_config->type); + } + + virMediatedDeviceAttrsCopy(dst_mdev_config, src_mdev_config); + + return 0; +} + + +static int +nodeDeviceDefCompareMdevs(virNodeDeviceDef *def_cur, + virNodeDeviceDef *def_upd, + bool live) +{ + virNodeDevCapsDef *caps = NULL; + virNodeDevCapMdev *cap_mdev_cur = NULL; + virNodeDevCapMdev *cap_mdev_upd = NULL; + + for (caps = def_cur->caps; caps != NULL; caps = caps->next) { + if (caps->data.type == VIR_NODE_DEV_CAP_MDEV) { + cap_mdev_cur = &caps->data.mdev; + } + } + if (!cap_mdev_cur) + return -1; + + for (caps = def_upd->caps; caps != NULL; caps = caps->next) { + if (caps->data.type == VIR_NODE_DEV_CAP_MDEV) { + cap_mdev_upd = &caps->data.mdev; + } + } + if (!cap_mdev_upd) + return -1; + + if (STRNEQ_NULLABLE(cap_mdev_cur->uuid, cap_mdev_upd->uuid)) { + virReportError(VIR_ERR_CONFIG_UNSUPPORTED, + _("Cannot update device '%1$s, uuid mismatch (current uuid '%2$s')"), + def_cur->name, + cap_mdev_cur->uuid); + return -1; + } + // for a live update the types of the active configs must match! + if (live && + STRNEQ_NULLABLE(cap_mdev_cur->active_config.type, cap_mdev_upd->active_config.type)) { + virReportError(VIR_ERR_CONFIG_UNSUPPORTED, + _("Cannot update device '%1$s', type mismatch (current type '%2$s')"), + def_cur->name, + cap_mdev_cur->active_config.type); + return -1; + } + if (STRNEQ_NULLABLE(cap_mdev_cur->parent_addr, cap_mdev_upd->parent_addr)) { + virReportError(VIR_ERR_CONFIG_UNSUPPORTED, + _("Cannot update device '%1$s', parent address mismatch (current parent address '%2$s')"), + def_cur->name, + cap_mdev_cur->parent_addr); + return -1; + } + + return 0; +} + + +int +nodeDeviceUpdateXML(virNodeDevice *device, + const char *xmlDesc, + unsigned int flags) +{ + int ret = -1; + virNodeDeviceObj *obj = NULL; + virNodeDeviceDef *def_cur; + g_autoptr(virNodeDeviceDef) def_upd = NULL > + const char *virt_type = NULL; + bool updated = false; + + virCheckFlags(VIR_NODE_DEVICE_UPDATE_XML_AFFECT_LIVE | + VIR_NODE_DEVICE_UPDATE_XML_AFFECT_CONFIG, -1); + + if (nodeDeviceInitWait() < 0) + return -1; + + if (!(obj = nodeDeviceObjFindByName(device->name))) + return -1; + def_cur = virNodeDeviceObjGetDef(obj); + + virt_type = virConnectGetType(device->conn); + + if (virNodeDeviceUpdateXMLEnsureACL(device->conn, def_cur, flags) < 0) + goto cleanup; + + if (!(def_upd = virNodeDeviceDefParse(xmlDesc, NULL, EXISTING_DEVICE, virt_type, + &driver->parserCallbacks, NULL, true))) + goto cleanup; + + if (nodeDeviceHasCapability(def_cur, VIR_NODE_DEV_CAP_MDEV) && + nodeDeviceHasCapability(def_upd, VIR_NODE_DEV_CAP_MDEV)) { + // Checks flags are valid for the state and sets flags for current if flags not set + if (virNodeDeviceObjUpdateModificationImpact(obj, &flags) < 0) + goto cleanup; + + // Before live update clone defined to active config + if (flags & VIR_NODE_DEVICE_UPDATE_XML_AFFECT_LIVE && + nodeDeviceDefCloneMdevConfigs(def_upd) < 0) + goto cleanup;
It's not clear to me why this is necessary. As far as I can tell, only the defined_config is used to generate the JSON to send to mdevctl. And this def is freed at the end of the function and then we re-query mdevctl to update our device list.
In case a live update is to be made there is a check of the mdev types also on the active config. Therefore I try to match the def used for the update to the current def by cloning the defined_config into the active_config. Another solution would have been to change the compare in nodeDeviceDefCloneMdevConfigs from STRNEQ_NULLABLE(cap_mdev_cur->active_config.type, cap_mdev_upd->active_config.type) into STRNEQ_NULLABLE(cap_mdev_cur->active_config.type, cap_mdev_upd->defined_config.type) which seems kind of wrong to me but I am willing to suffer if you disagree... :D
+ + // Compare def_cur and def_upd for compatibleness e.g. parent, type and uuid
compatibleness -> compatibility
Changed.
+ if (nodeDeviceDefCompareMdevs(def_cur, def_upd, + (flags & VIR_NODE_DEVICE_UPDATE_XML_AFFECT_LIVE)) < 0) + goto cleanup; + + // Update now + VIR_DEBUG("Update node device '%s' with mdevctl", def_cur->name); + if (virMdevctlModify(def_upd, + (flags & VIR_NODE_DEVICE_UPDATE_XML_AFFECT_CONFIG), + (flags & VIR_NODE_DEVICE_UPDATE_XML_AFFECT_LIVE)) < 0) { + goto cleanup; + }; + // Detect updates and also trigger events + updated = true; + } else { + virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s", + _("Unsupported device type")); + goto cleanup; + } + + ret = 0; + cleanup: + virNodeDeviceObjEndAPI(&obj); + if (updated) + nodeDeviceUpdateMediatedDevices(); + + return ret; +} diff --git a/src/node_device/node_device_driver.h b/src/node_device/node_device_driver.h index 4dce7e6f17..5751625eda 100644 --- a/src/node_device/node_device_driver.h +++ b/src/node_device/node_device_driver.h @@ -39,6 +39,7 @@ typedef enum { * separation makes our code more readable in terms of knowing when we're * starting a defined device and when we're creating a transient one */ MDEVCTL_CMD_CREATE, + MDEVCTL_CMD_MODIFY, MDEVCTL_CMD_LAST, } virMdevctlCommand; @@ -186,3 +187,13 @@ virCommand* nodeDeviceGetMdevctlSetAutostartCommand(virNodeDeviceDef *def, bool autostart, char **errmsg); + +virCommand* +nodeDeviceGetMdevctlModifyCommand(virNodeDeviceDef *def, + bool defined, + bool live, + char **errmsg); +int +nodeDeviceUpdateXML(virNodeDevice *dev, + const char *xmlDesc, + unsigned int flags); diff --git a/src/node_device/node_device_udev.c b/src/node_device/node_device_udev.c index 57368a96c3..4cd9d285fa 100644 --- a/src/node_device/node_device_udev.c +++ b/src/node_device/node_device_udev.c @@ -2403,6 +2403,7 @@ static virNodeDeviceDriver udevNodeDeviceDriver = { .nodeDeviceGetAutostart = nodeDeviceGetAutostart, /* 7.8.0 */ .nodeDeviceIsPersistent = nodeDeviceIsPersistent, /* 7.8.0 */ .nodeDeviceIsActive = nodeDeviceIsActive, /* 7.8.0 */ + .nodeDeviceUpdateXML = nodeDeviceUpdateXML, /* 10.1.0 */ }; diff --git a/tests/nodedevmdevctldata/mdev_c60cc60c_c60c_c60c_c60c_c60cc60cc60c_update.xml b/tests/nodedevmdevctldata/mdev_c60cc60c_c60c_c60c_c60c_c60cc60cc60c_update.xml new file mode 100644 index 0000000000..17e3611bf4 --- /dev/null +++ b/tests/nodedevmdevctldata/mdev_c60cc60c_c60c_c60c_c60c_c60cc60cc60c_update.xml @@ -0,0 +1,16 @@ +<device> + <name>mdev_c60cc60c_c60c_c60c_c60c_c60cc60cc60c_0_0_0052</name> + <path>/sys/devices/css0/0.0.0052/c60cc60c-c60c-c60c-c60c-c60cc60cc60c</path> + <parent>css_0_0_0052</parent> + <driver> + <name>vfio_ccw_mdev</name> + </driver> + <capability type='mdev'> + <type id='vfio_ccw-io'/> + <uuid>c60cc60c-c60c-c60c-c60c-c60cc60cc60c</uuid> + <parent_addr>0.0.0052</parent_addr> + <attr name='add_attr_1' value='val1'/> + <attr name='add_attr_2' value='val2'/> + <iommuGroup number='4'/> + </capability> +</device> diff --git a/tests/nodedevmdevctldata/mdevctl-modify.argv b/tests/nodedevmdevctldata/mdevctl-modify.argv new file mode 100644 index 0000000000..341c5e7fd0 --- /dev/null +++ b/tests/nodedevmdevctldata/mdevctl-modify.argv @@ -0,0 +1,25 @@ +mdevctl \ +modify \ +--parent=0.0.0052 \ +--jsonfile=/dev/stdin \ +--uuid=c60cc60c-c60c-c60c-c60c-c60cc60cc60c \ +--defined +mdevctl \ +modify \ +--parent=0.0.0052 \ +--jsonfile=/dev/stdin \ +--uuid=c60cc60c-c60c-c60c-c60c-c60cc60cc60c \ +--live +mdevctl \ +modify \ +--parent=0.0.0052 \ +--jsonfile=/dev/stdin \ +--uuid=c60cc60c-c60c-c60c-c60c-c60cc60cc60c \ +--live +mdevctl \ +modify \ +--parent=0.0.0052 \ +--jsonfile=/dev/stdin \ +--uuid=c60cc60c-c60c-c60c-c60c-c60cc60cc60c \ +--defined \ +--live diff --git a/tests/nodedevmdevctldata/mdevctl-modify.json b/tests/nodedevmdevctldata/mdevctl-modify.json new file mode 100644 index 0000000000..7ae8c73ce3 --- /dev/null +++ b/tests/nodedevmdevctldata/mdevctl-modify.json @@ -0,0 +1 @@ +{"mdev_type":"vfio_ccw-io","start":"manual"}{"mdev_type":"vfio_ccw-io","start":"manual","attrs":[{"add_attr_1":"val1"},{"add_attr_2":"val2"}]}{"mdev_type":"vfio_ccw-io","start":"manual"}{"mdev_type":"vfio_ccw-io","start":"manual","attrs":[{"add_attr_1":"val1"},{"add_attr_2":"val2"}]} diff --git a/tests/nodedevmdevctltest.c b/tests/nodedevmdevctltest.c index f49d668461..11273e3049 100644 --- a/tests/nodedevmdevctltest.c +++ b/tests/nodedevmdevctltest.c @@ -33,7 +33,10 @@ testCommandDryRunCallback(const char *const*args G_GNUC_UNUSED, { char **stdinbuf = opaque; - *stdinbuf = g_strdup(input); + if (*stdinbuf) + *stdinbuf = g_strconcat(*stdinbuf, input, NULL);
I think g_strjoin() would be nicer so we can add a newline separator between invocations.
There is no problem adding a newline seperator with the g_strconcat as well. Using g_strconcat kind of looks more natural to me *stdinbuf = g_strconcat(*stdinbuf, "\n", input, NULL); versus *stdinbuf = g_strjoin("\n", *stdinbuf, input, NULL);
+ else + *stdinbuf = g_strdup(input); } typedef virCommand * (*MdevctlCmdFunc)(virNodeDeviceDef *, char **, char **); @@ -63,6 +66,7 @@ testMdevctlCmd(virMdevctlCommand cmd_type, case MDEVCTL_CMD_START: case MDEVCTL_CMD_STOP: case MDEVCTL_CMD_UNDEFINE: + case MDEVCTL_CMD_MODIFY: create = EXISTING_DEVICE; break; case MDEVCTL_CMD_LAST: @@ -173,6 +177,85 @@ testMdevctlAutostart(const void *data G_GNUC_UNUSED) return ret; } + +static int +testMdevctlModify(const void *data G_GNUC_UNUSED) +{ + g_autoptr(virNodeDeviceDef) def = NULL; + g_autoptr(virNodeDeviceDef) def_update = NULL; + virBuffer buf = VIR_BUFFER_INITIALIZER; + const char *actualCmdline = NULL; + int ret = -1; + g_autoptr(virCommand) definedcmd = NULL; + g_autoptr(virCommand) livecmd = NULL; + g_autoptr(virCommand) bothcmd = NULL; + g_autofree char *errmsg = NULL; + g_autofree char *stdinbuf = NULL; + g_autofree char *mdevxml = + g_strdup_printf("%s/nodedevschemadata/mdev_c60cc60c_c60c_c60c_c60c_c60cc60cc60c.xml", + abs_srcdir); + g_autofree char *mdevxml_update = + g_strdup_printf("%s/nodedevmdevctldata/mdev_c60cc60c_c60c_c60c_c60c_c60cc60cc60c_update.xml", + abs_srcdir); + /* just concatenate both calls into the same output files */ + g_autofree char *cmdlinefile = + g_strdup_printf("%s/nodedevmdevctldata/mdevctl-modify.argv", + abs_srcdir); + g_autofree char *jsonfile = + g_strdup_printf("%s/nodedevmdevctldata/mdevctl-modify.json", + abs_srcdir); + g_autoptr(virCommandDryRunToken) dryRunToken = virCommandDryRunTokenNew(); + + if (!(def = virNodeDeviceDefParse(NULL, mdevxml, CREATE_DEVICE, VIRT_TYPE, + &parser_callbacks, NULL, false))) + return -1; + + virCommandSetDryRun(dryRunToken, &buf, true, true, testCommandDryRunCallback, &stdinbuf); + + if (!(definedcmd = nodeDeviceGetMdevctlModifyCommand(def, true, false, &errmsg))) + goto cleanup; + + if (virCommandRun(definedcmd, NULL) < 0) + goto cleanup; + + if (!(def_update = virNodeDeviceDefParse(NULL, mdevxml_update, EXISTING_DEVICE, VIRT_TYPE, + &parser_callbacks, NULL, false))) + goto cleanup; + + if (!(livecmd = nodeDeviceGetMdevctlModifyCommand(def_update, false, true, &errmsg))) + goto cleanup; + + if (virCommandRun(livecmd, NULL) < 0) + goto cleanup; + + if (!(livecmd = nodeDeviceGetMdevctlModifyCommand(def, false, true, &errmsg))) + goto cleanup; + + if (virCommandRun(livecmd, NULL) < 0) + goto cleanup; + + if (!(bothcmd = nodeDeviceGetMdevctlModifyCommand(def_update, true, true, &errmsg))) + goto cleanup; + + if (virCommandRun(bothcmd, NULL) < 0) + goto cleanup; + + if (!(actualCmdline = virBufferCurrentContent(&buf))) + goto cleanup; + + if (virTestCompareToFileFull(actualCmdline, cmdlinefile, false) < 0) + goto cleanup; + + if (virTestCompareToFile(stdinbuf, jsonfile) < 0) + goto cleanup; + + ret = 0; + + cleanup: + virBufferFreeAndReset(&buf); + return ret; +} + static int testMdevctlListDefined(const void *data G_GNUC_UNUSED) { @@ -457,6 +540,9 @@ mymain(void) #define DO_TEST_AUTOSTART() \ DO_TEST_FULL("autostart mdevs", testMdevctlAutostart, NULL) +#define DO_TEST_MODIFY() \ + DO_TEST_FULL("modify mdevs", testMdevctlModify, NULL) + #define DO_TEST_PARSE_JSON(filename) \ DO_TEST_FULL("parse mdevctl json " filename, testMdevctlParse, filename) @@ -485,6 +571,8 @@ mymain(void) DO_TEST_AUTOSTART(); + DO_TEST_MODIFY(); + done: nodedevTestDriverFree(driver);
Reviewed-by: Jonathon Jongsma <jjongsma@redhat.com>
Thanks.
_______________________________________________ Devel mailing list -- devel@lists.libvirt.org To unsubscribe send an email to devel-leave@lists.libvirt.org
-- Mit freundlichen Grüßen/Kind regards Boris Fiuczynski IBM Deutschland Research & Development GmbH Vorsitzender des Aufsichtsrats: Wolfgang Wendt Geschäftsführung: David Faller Sitz der Gesellschaft: Böblingen Registergericht: Amtsgericht Stuttgart, HRB 243294

On 2/13/24 3:04 AM, Boris Fiuczynski wrote:
On 2/9/24 23:45, Jonathon Jongsma wrote:
On 2/7/24 7:39 AM, Boris Fiuczynski wrote:
Implement the API functions in the node device driver by using mdevctl modify with the options defined and live. Instead of increasing the minimum mdevctl version to 1.3.0 in spec file to ensure support exists in mdevctl the support is dynamically checked before using mdevctl.
Signed-off-by: Boris Fiuczynski <fiuczy@linux.ibm.com> --- NEWS.rst | 7 + libvirt.spec.in | 1 + src/node_device/node_device_driver.c | 256 +++++++++++++++++- src/node_device/node_device_driver.h | 11 + src/node_device/node_device_udev.c | 1 + ...60c_c60c_c60c_c60c_c60cc60cc60c_update.xml | 16 ++ tests/nodedevmdevctldata/mdevctl-modify.argv | 25 ++ tests/nodedevmdevctldata/mdevctl-modify.json | 1 + tests/nodedevmdevctltest.c | 90 +++++- 9 files changed, 405 insertions(+), 3 deletions(-) create mode 100644 tests/nodedevmdevctldata/mdev_c60cc60c_c60c_c60c_c60c_c60cc60cc60c_update.xml create mode 100644 tests/nodedevmdevctldata/mdevctl-modify.argv create mode 100644 tests/nodedevmdevctldata/mdevctl-modify.json
diff --git a/NEWS.rst b/NEWS.rst index e2796fd8b2..aa2fee3533 100644 --- a/NEWS.rst +++ b/NEWS.rst @@ -17,6 +17,13 @@ v10.1.0 (unreleased) * **New features** + * nodedev: Support updating mdevs + + The node device driver has been extended to allow updating mediated node + devices. Options are available to target the update against the persisted, + active or both configurations of an mediated device. + **Note:** The support is only available with at least mdevctl v1.3.0 installed. + * qemu: Support clusters in CPU topology It is now possible to configure the guest CPU topology to use clusters. diff --git a/libvirt.spec.in b/libvirt.spec.in index 8413e3c19a..a8dff4a32e 100644 --- a/libvirt.spec.in +++ b/libvirt.spec.in @@ -613,6 +613,7 @@ Requires: libvirt-libs = %{version}-%{release} # needed for device enumeration Requires: systemd >= 185 # For managing persistent mediated devices +# Note: for nodedev-update support at least mdevctl v1.3.0 is required Requires: mdevctl # for modprobe of pci devices Requires: module-init-tools diff --git a/src/node_device/node_device_driver.c b/src/node_device/node_device_driver.c index baff49a0ae..5bece1a30a 100644 --- a/src/node_device/node_device_driver.c +++ b/src/node_device/node_device_driver.c @@ -54,7 +54,7 @@ virNodeDeviceDriverState *driver; VIR_ENUM_IMPL(virMdevctlCommand, MDEVCTL_CMD_LAST, - "start", "stop", "define", "undefine", "create" + "start", "stop", "define", "undefine", "create", "modify" ); @@ -754,6 +754,7 @@ nodeDeviceGetMdevctlCommand(virNodeDeviceDef *def, case MDEVCTL_CMD_START: case MDEVCTL_CMD_DEFINE: case MDEVCTL_CMD_UNDEFINE: + case MDEVCTL_CMD_MODIFY: cmd = virCommandNewArgList(MDEVCTL, subcommand, NULL); break; case MDEVCTL_CMD_LAST: @@ -767,6 +768,7 @@ nodeDeviceGetMdevctlCommand(virNodeDeviceDef *def, switch (cmd_type) { case MDEVCTL_CMD_CREATE: case MDEVCTL_CMD_DEFINE: + case MDEVCTL_CMD_MODIFY: if (!def->caps->data.mdev.parent_addr) { virReportError(VIR_ERR_INTERNAL_ERROR, _("unable to find parent device '%1$s'"), def->parent); @@ -783,7 +785,8 @@ nodeDeviceGetMdevctlCommand(virNodeDeviceDef *def, virCommandAddArgPair(cmd, "--jsonfile", "/dev/stdin"); virCommandSetInputBuffer(cmd, inbuf); - virCommandSetOutputBuffer(cmd, outbuf); + if (outbuf) + virCommandSetOutputBuffer(cmd, outbuf); break; case MDEVCTL_CMD_UNDEFINE: @@ -868,6 +871,100 @@ virMdevctlDefine(virNodeDeviceDef *def, char **uuid) } +/* gets a virCommand object that executes a mdevctl command to modify a + * a device to an updated version + */ +virCommand* +nodeDeviceGetMdevctlModifyCommand(virNodeDeviceDef *def, + bool defined, + bool live, + char **errmsg) +{ + virCommand *cmd = nodeDeviceGetMdevctlCommand(def, + MDEVCTL_CMD_MODIFY, + NULL, errmsg); + + if (!cmd) + return NULL; + + if (defined) + virCommandAddArg(cmd, "--defined"); + + if (live) + virCommandAddArg(cmd, "--live"); + + return cmd; +} + + +/* checks if mdevctl supports on the command modify the options live, defined + * and jsonfile + */ +static int +nodeDeviceGetMdevctlModifySupportCheck(void) +{ + int status; + g_autoptr(virCommand) cmd = NULL; + const char *subcommand = virMdevctlCommandTypeToString(MDEVCTL_CMD_MODIFY); + + cmd = virCommandNewArgList(MDEVCTL, + subcommand, + "--defined", + "--live", + "--jsonfile", + "b", + "--help", + NULL); + + if (!cmd) + return -1; + + if (virCommandRun(cmd, &status) < 0) + return -1; + + if (status != 0) // update is unsupported + return -1; + + return 0; +} + + +static int +virMdevctlModify(virNodeDeviceDef *def, + bool defined, + bool live) +{ + int status; + g_autofree char *errmsg = NULL; + g_autoptr(virCommand) cmd = nodeDeviceGetMdevctlModifyCommand(def, + defined, + live, + &errmsg); + + if (!cmd) + return -1; + + if (nodeDeviceGetMdevctlModifySupportCheck() < 0) { + VIR_WARN("Installed mdevctl version does not support modify with options jsonfile, defined and live"); + virReportError(VIR_ERR_OPERATION_UNSUPPORTED, "%s", + _("Unable to modify mediated device: modify unsupported")); + return -1; + } + + if (virCommandRun(cmd, &status) < 0) + return -1; + + if (status != 0) { + virReportError(VIR_ERR_INTERNAL_ERROR, + _("Unable to modify mediated device: %1$s"), + MDEVCTL_ERROR(errmsg)); + return -1; + } + + return 0; +} + + static virNodeDevicePtr nodeDeviceCreateXMLMdev(virConnectPtr conn, virNodeDeviceDef *def) @@ -2106,3 +2203,158 @@ nodeDeviceIsActive(virNodeDevice *device) virNodeDeviceObjEndAPI(&obj); return ret; } + + +static int +nodeDeviceDefCloneMdevConfigs(virNodeDeviceDef *def_upd) +{ + virNodeDevCapsDef *caps = NULL; + virNodeDevCapMdev *cap_mdev_upd = NULL; + virMediatedDeviceConfig *src_mdev_config = NULL; + virMediatedDeviceConfig *dst_mdev_config = NULL; + + for (caps = def_upd->caps; caps != NULL; caps = caps->next) { + if (caps->data.type == VIR_NODE_DEV_CAP_MDEV) { + cap_mdev_upd = &caps->data.mdev; + } + } + if (!cap_mdev_upd) + return -1; + + src_mdev_config = &cap_mdev_upd->defined_config; + dst_mdev_config = &cap_mdev_upd->active_config; + + if (STRNEQ_NULLABLE(dst_mdev_config->type, src_mdev_config->type)) { + g_free(dst_mdev_config->type); + dst_mdev_config->type = g_strdup(src_mdev_config->type); + } + + virMediatedDeviceAttrsCopy(dst_mdev_config, src_mdev_config); + + return 0; +} + +
I'm making a couple additional suggestions here relevant to our discussion below.
+static int +nodeDeviceDefCompareMdevs(virNodeDeviceDef *def_cur, + virNodeDeviceDef *def_upd, + bool live)
I think this function is poorly named. It implies that you are comparing two different mdevs for equality, but it's really a function that validates whether it is valid to update from one device def to a new def. So it would be much more understandable if it was named something like nodeDeviceDefValidateUpdate(). In addition, I'd personally appreciate less cryptic variable names. For example, `def_cur` could just be `def` or `current_def`. `def_upd` could be `new_def`. Similar for the local variables below.
+{ + virNodeDevCapsDef *caps = NULL; + virNodeDevCapMdev *cap_mdev_cur = NULL; + virNodeDevCapMdev *cap_mdev_upd = NULL; + + for (caps = def_cur->caps; caps != NULL; caps = caps->next) { + if (caps->data.type == VIR_NODE_DEV_CAP_MDEV) { + cap_mdev_cur = &caps->data.mdev; + } + } + if (!cap_mdev_cur) + return -1; + + for (caps = def_upd->caps; caps != NULL; caps = caps->next) { + if (caps->data.type == VIR_NODE_DEV_CAP_MDEV) { + cap_mdev_upd = &caps->data.mdev; + } + } + if (!cap_mdev_upd) + return -1; + + if (STRNEQ_NULLABLE(cap_mdev_cur->uuid, cap_mdev_upd->uuid)) { + virReportError(VIR_ERR_CONFIG_UNSUPPORTED, + _("Cannot update device '%1$s, uuid mismatch (current uuid '%2$s')"), + def_cur->name, + cap_mdev_cur->uuid); + return -1; + } + // for a live update the types of the active configs must match!
Related to my suggestion below, you could change the comment here to something like: /* A live update cannot change the mdev type. Since the new config is * stored in defined_config, compare that to the mdev type of the * current live config to make sure they match */ Then it would be clear why you're comparing the current active config to the new defined config.
+ if (live && + STRNEQ_NULLABLE(cap_mdev_cur->active_config.type, cap_mdev_upd->active_config.type)) { + virReportError(VIR_ERR_CONFIG_UNSUPPORTED, + _("Cannot update device '%1$s', type mismatch (current type '%2$s')"), + def_cur->name, + cap_mdev_cur->active_config.type); + return -1; + } + if (STRNEQ_NULLABLE(cap_mdev_cur->parent_addr, cap_mdev_upd->parent_addr)) { + virReportError(VIR_ERR_CONFIG_UNSUPPORTED, + _("Cannot update device '%1$s', parent address mismatch (current parent address '%2$s')"), + def_cur->name, + cap_mdev_cur->parent_addr); + return -1; + } + + return 0; +} + + +int +nodeDeviceUpdateXML(virNodeDevice *device, + const char *xmlDesc, + unsigned int flags) +{ + int ret = -1; + virNodeDeviceObj *obj = NULL; + virNodeDeviceDef *def_cur; + g_autoptr(virNodeDeviceDef) def_upd = NULL
^ another case where less cryptic variable names would be nice.
+ bool updated = false; + + virCheckFlags(VIR_NODE_DEVICE_UPDATE_XML_AFFECT_LIVE | + VIR_NODE_DEVICE_UPDATE_XML_AFFECT_CONFIG, -1); + + if (nodeDeviceInitWait() < 0) + return -1; + + if (!(obj = nodeDeviceObjFindByName(device->name))) + return -1; + def_cur = virNodeDeviceObjGetDef(obj); + + virt_type = virConnectGetType(device->conn); + + if (virNodeDeviceUpdateXMLEnsureACL(device->conn, def_cur, flags) < 0) + goto cleanup; + + if (!(def_upd = virNodeDeviceDefParse(xmlDesc, NULL, EXISTING_DEVICE, virt_type, + &driver->parserCallbacks, NULL, true))) + goto cleanup; + + if (nodeDeviceHasCapability(def_cur, VIR_NODE_DEV_CAP_MDEV) && + nodeDeviceHasCapability(def_upd, VIR_NODE_DEV_CAP_MDEV)) { + // Checks flags are valid for the state and sets flags for current if flags not set + if (virNodeDeviceObjUpdateModificationImpact(obj, &flags) < 0) + goto cleanup; + + // Before live update clone defined to active config + if (flags & VIR_NODE_DEVICE_UPDATE_XML_AFFECT_LIVE && + nodeDeviceDefCloneMdevConfigs(def_upd) < 0) + goto cleanup;
It's not clear to me why this is necessary. As far as I can tell, only the defined_config is used to generate the JSON to send to mdevctl. And this def is freed at the end of the function and then we re-query mdevctl to update our device list.
In case a live update is to be made there is a check of the mdev types also on the active config. Therefore I try to match the def used for the update to the current def by cloning the defined_config into the active_config. Another solution would have been to change the compare in nodeDeviceDefCloneMdevConfigs from STRNEQ_NULLABLE(cap_mdev_cur->active_config.type, cap_mdev_upd->active_config.type) into STRNEQ_NULLABLE(cap_mdev_cur->active_config.type, cap_mdev_upd->defined_config.type) which seems kind of wrong to me but I am willing to suffer if you disagree... :D
I assume you mean the function nodeDeviceDefCompareMdevs(). I do think that would be a better approach, actually. It does look a bit weird, but with a clear comment in the function (suggestion above), I think it's more understandable than the alternative of calling one function to copy the persistent config into the active config only for the purposes of checking it in another function. Jonathon
+ + // Compare def_cur and def_upd for compatibleness e.g. parent, type and uuid
compatibleness -> compatibility
Changed.
+ if (nodeDeviceDefCompareMdevs(def_cur, def_upd, + (flags & VIR_NODE_DEVICE_UPDATE_XML_AFFECT_LIVE)) < 0) + goto cleanup; + + // Update now + VIR_DEBUG("Update node device '%s' with mdevctl", def_cur->name); + if (virMdevctlModify(def_upd, + (flags & VIR_NODE_DEVICE_UPDATE_XML_AFFECT_CONFIG), + (flags & VIR_NODE_DEVICE_UPDATE_XML_AFFECT_LIVE)) < 0) { + goto cleanup; + }; + // Detect updates and also trigger events + updated = true; + } else { + virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s", + _("Unsupported device type")); + goto cleanup; + } + + ret = 0; + cleanup: + virNodeDeviceObjEndAPI(&obj); + if (updated) + nodeDeviceUpdateMediatedDevices(); + + return ret; +} diff --git a/src/node_device/node_device_driver.h b/src/node_device/node_device_driver.h index 4dce7e6f17..5751625eda 100644 --- a/src/node_device/node_device_driver.h +++ b/src/node_device/node_device_driver.h @@ -39,6 +39,7 @@ typedef enum { * separation makes our code more readable in terms of knowing when we're * starting a defined device and when we're creating a transient one */ MDEVCTL_CMD_CREATE, + MDEVCTL_CMD_MODIFY, MDEVCTL_CMD_LAST, } virMdevctlCommand; @@ -186,3 +187,13 @@ virCommand* nodeDeviceGetMdevctlSetAutostartCommand(virNodeDeviceDef *def, bool autostart, char **errmsg); + +virCommand* +nodeDeviceGetMdevctlModifyCommand(virNodeDeviceDef *def, + bool defined, + bool live, + char **errmsg); +int +nodeDeviceUpdateXML(virNodeDevice *dev, + const char *xmlDesc, + unsigned int flags); diff --git a/src/node_device/node_device_udev.c b/src/node_device/node_device_udev.c index 57368a96c3..4cd9d285fa 100644 --- a/src/node_device/node_device_udev.c +++ b/src/node_device/node_device_udev.c @@ -2403,6 +2403,7 @@ static virNodeDeviceDriver udevNodeDeviceDriver = { .nodeDeviceGetAutostart = nodeDeviceGetAutostart, /* 7.8.0 */ .nodeDeviceIsPersistent = nodeDeviceIsPersistent, /* 7.8.0 */ .nodeDeviceIsActive = nodeDeviceIsActive, /* 7.8.0 */ + .nodeDeviceUpdateXML = nodeDeviceUpdateXML, /* 10.1.0 */ }; diff --git a/tests/nodedevmdevctldata/mdev_c60cc60c_c60c_c60c_c60c_c60cc60cc60c_update.xml b/tests/nodedevmdevctldata/mdev_c60cc60c_c60c_c60c_c60c_c60cc60cc60c_update.xml new file mode 100644 index 0000000000..17e3611bf4 --- /dev/null +++ b/tests/nodedevmdevctldata/mdev_c60cc60c_c60c_c60c_c60c_c60cc60cc60c_update.xml @@ -0,0 +1,16 @@ +<device> + <name>mdev_c60cc60c_c60c_c60c_c60c_c60cc60cc60c_0_0_0052</name> + <path>/sys/devices/css0/0.0.0052/c60cc60c-c60c-c60c-c60c-c60cc60cc60c</path> + <parent>css_0_0_0052</parent> + <driver> + <name>vfio_ccw_mdev</name> + </driver> + <capability type='mdev'> + <type id='vfio_ccw-io'/> + <uuid>c60cc60c-c60c-c60c-c60c-c60cc60cc60c</uuid> + <parent_addr>0.0.0052</parent_addr> + <attr name='add_attr_1' value='val1'/> + <attr name='add_attr_2' value='val2'/> + <iommuGroup number='4'/> + </capability> +</device> diff --git a/tests/nodedevmdevctldata/mdevctl-modify.argv b/tests/nodedevmdevctldata/mdevctl-modify.argv new file mode 100644 index 0000000000..341c5e7fd0 --- /dev/null +++ b/tests/nodedevmdevctldata/mdevctl-modify.argv @@ -0,0 +1,25 @@ +mdevctl \ +modify \ +--parent=0.0.0052 \ +--jsonfile=/dev/stdin \ +--uuid=c60cc60c-c60c-c60c-c60c-c60cc60cc60c \ +--defined +mdevctl \ +modify \ +--parent=0.0.0052 \ +--jsonfile=/dev/stdin \ +--uuid=c60cc60c-c60c-c60c-c60c-c60cc60cc60c \ +--live +mdevctl \ +modify \ +--parent=0.0.0052 \ +--jsonfile=/dev/stdin \ +--uuid=c60cc60c-c60c-c60c-c60c-c60cc60cc60c \ +--live +mdevctl \ +modify \ +--parent=0.0.0052 \ +--jsonfile=/dev/stdin \ +--uuid=c60cc60c-c60c-c60c-c60c-c60cc60cc60c \ +--defined \ +--live diff --git a/tests/nodedevmdevctldata/mdevctl-modify.json b/tests/nodedevmdevctldata/mdevctl-modify.json new file mode 100644 index 0000000000..7ae8c73ce3 --- /dev/null +++ b/tests/nodedevmdevctldata/mdevctl-modify.json @@ -0,0 +1 @@ +{"mdev_type":"vfio_ccw-io","start":"manual"}{"mdev_type":"vfio_ccw-io","start":"manual","attrs":[{"add_attr_1":"val1"},{"add_attr_2":"val2"}]}{"mdev_type":"vfio_ccw-io","start":"manual"}{"mdev_type":"vfio_ccw-io","start":"manual","attrs":[{"add_attr_1":"val1"},{"add_attr_2":"val2"}]} diff --git a/tests/nodedevmdevctltest.c b/tests/nodedevmdevctltest.c index f49d668461..11273e3049 100644 --- a/tests/nodedevmdevctltest.c +++ b/tests/nodedevmdevctltest.c @@ -33,7 +33,10 @@ testCommandDryRunCallback(const char *const*args G_GNUC_UNUSED, { char **stdinbuf = opaque; - *stdinbuf = g_strdup(input); + if (*stdinbuf) + *stdinbuf = g_strconcat(*stdinbuf, input, NULL);
I think g_strjoin() would be nicer so we can add a newline separator between invocations.
There is no problem adding a newline seperator with the g_strconcat as well. Using g_strconcat kind of looks more natural to me
*stdinbuf = g_strconcat(*stdinbuf, "\n", input, NULL);
versus
*stdinbuf = g_strjoin("\n", *stdinbuf, input, NULL);
+ else + *stdinbuf = g_strdup(input); } typedef virCommand * (*MdevctlCmdFunc)(virNodeDeviceDef *, char **, char **); @@ -63,6 +66,7 @@ testMdevctlCmd(virMdevctlCommand cmd_type, case MDEVCTL_CMD_START: case MDEVCTL_CMD_STOP: case MDEVCTL_CMD_UNDEFINE: + case MDEVCTL_CMD_MODIFY: create = EXISTING_DEVICE; break; case MDEVCTL_CMD_LAST: @@ -173,6 +177,85 @@ testMdevctlAutostart(const void *data G_GNUC_UNUSED) return ret; } + +static int +testMdevctlModify(const void *data G_GNUC_UNUSED) +{ + g_autoptr(virNodeDeviceDef) def = NULL; + g_autoptr(virNodeDeviceDef) def_update = NULL; + virBuffer buf = VIR_BUFFER_INITIALIZER; + const char *actualCmdline = NULL; + int ret = -1; + g_autoptr(virCommand) definedcmd = NULL; + g_autoptr(virCommand) livecmd = NULL; + g_autoptr(virCommand) bothcmd = NULL; + g_autofree char *errmsg = NULL; + g_autofree char *stdinbuf = NULL; + g_autofree char *mdevxml = + g_strdup_printf("%s/nodedevschemadata/mdev_c60cc60c_c60c_c60c_c60c_c60cc60cc60c.xml", + abs_srcdir); + g_autofree char *mdevxml_update = + g_strdup_printf("%s/nodedevmdevctldata/mdev_c60cc60c_c60c_c60c_c60c_c60cc60cc60c_update.xml", + abs_srcdir); + /* just concatenate both calls into the same output files */ + g_autofree char *cmdlinefile = + g_strdup_printf("%s/nodedevmdevctldata/mdevctl-modify.argv", + abs_srcdir); + g_autofree char *jsonfile = + g_strdup_printf("%s/nodedevmdevctldata/mdevctl-modify.json", + abs_srcdir); + g_autoptr(virCommandDryRunToken) dryRunToken = virCommandDryRunTokenNew(); + + if (!(def = virNodeDeviceDefParse(NULL, mdevxml, CREATE_DEVICE, VIRT_TYPE, + &parser_callbacks, NULL, false))) + return -1; + + virCommandSetDryRun(dryRunToken, &buf, true, true, testCommandDryRunCallback, &stdinbuf); + + if (!(definedcmd = nodeDeviceGetMdevctlModifyCommand(def, true, false, &errmsg))) + goto cleanup; + + if (virCommandRun(definedcmd, NULL) < 0) + goto cleanup; + + if (!(def_update = virNodeDeviceDefParse(NULL, mdevxml_update, EXISTING_DEVICE, VIRT_TYPE, + &parser_callbacks, NULL, false))) + goto cleanup; + + if (!(livecmd = nodeDeviceGetMdevctlModifyCommand(def_update, false, true, &errmsg))) + goto cleanup; + + if (virCommandRun(livecmd, NULL) < 0) + goto cleanup; + + if (!(livecmd = nodeDeviceGetMdevctlModifyCommand(def, false, true, &errmsg))) + goto cleanup; + + if (virCommandRun(livecmd, NULL) < 0) + goto cleanup; + + if (!(bothcmd = nodeDeviceGetMdevctlModifyCommand(def_update, true, true, &errmsg))) + goto cleanup; + + if (virCommandRun(bothcmd, NULL) < 0) + goto cleanup; + + if (!(actualCmdline = virBufferCurrentContent(&buf))) + goto cleanup; + + if (virTestCompareToFileFull(actualCmdline, cmdlinefile, false) < 0) + goto cleanup; + + if (virTestCompareToFile(stdinbuf, jsonfile) < 0) + goto cleanup; + + ret = 0; + + cleanup: + virBufferFreeAndReset(&buf); + return ret; +} + static int testMdevctlListDefined(const void *data G_GNUC_UNUSED) { @@ -457,6 +540,9 @@ mymain(void) #define DO_TEST_AUTOSTART() \ DO_TEST_FULL("autostart mdevs", testMdevctlAutostart, NULL) +#define DO_TEST_MODIFY() \ + DO_TEST_FULL("modify mdevs", testMdevctlModify, NULL) + #define DO_TEST_PARSE_JSON(filename) \ DO_TEST_FULL("parse mdevctl json " filename, testMdevctlParse, filename) @@ -485,6 +571,8 @@ mymain(void) DO_TEST_AUTOSTART(); + DO_TEST_MODIFY(); + done: nodedevTestDriverFree(driver);
Reviewed-by: Jonathon Jongsma <jjongsma@redhat.com>
Thanks.
_______________________________________________ Devel mailing list -- devel@lists.libvirt.org To unsubscribe send an email to devel-leave@lists.libvirt.org

On 2/14/24 20:07, Jonathon Jongsma wrote:
On 2/13/24 3:04 AM, Boris Fiuczynski wrote:
On 2/9/24 23:45, Jonathon Jongsma wrote:
On 2/7/24 7:39 AM, Boris Fiuczynski wrote:
Implement the API functions in the node device driver by using mdevctl modify with the options defined and live. Instead of increasing the minimum mdevctl version to 1.3.0 in spec file to ensure support exists in mdevctl the support is dynamically checked before using mdevctl.
Signed-off-by: Boris Fiuczynski <fiuczy@linux.ibm.com> --- NEWS.rst | 7 + libvirt.spec.in | 1 + src/node_device/node_device_driver.c | 256 +++++++++++++++++- src/node_device/node_device_driver.h | 11 + src/node_device/node_device_udev.c | 1 + ...60c_c60c_c60c_c60c_c60cc60cc60c_update.xml | 16 ++ tests/nodedevmdevctldata/mdevctl-modify.argv | 25 ++ tests/nodedevmdevctldata/mdevctl-modify.json | 1 + tests/nodedevmdevctltest.c | 90 +++++- 9 files changed, 405 insertions(+), 3 deletions(-) create mode 100644 tests/nodedevmdevctldata/mdev_c60cc60c_c60c_c60c_c60c_c60cc60cc60c_update.xml create mode 100644 tests/nodedevmdevctldata/mdevctl-modify.argv create mode 100644 tests/nodedevmdevctldata/mdevctl-modify.json
diff --git a/NEWS.rst b/NEWS.rst index e2796fd8b2..aa2fee3533 100644 --- a/NEWS.rst +++ b/NEWS.rst @@ -17,6 +17,13 @@ v10.1.0 (unreleased) * **New features** + * nodedev: Support updating mdevs + + The node device driver has been extended to allow updating mediated node + devices. Options are available to target the update against the persisted, + active or both configurations of an mediated device. + **Note:** The support is only available with at least mdevctl v1.3.0 installed. + * qemu: Support clusters in CPU topology It is now possible to configure the guest CPU topology to use clusters. diff --git a/libvirt.spec.in b/libvirt.spec.in index 8413e3c19a..a8dff4a32e 100644 --- a/libvirt.spec.in +++ b/libvirt.spec.in @@ -613,6 +613,7 @@ Requires: libvirt-libs = %{version}-%{release} # needed for device enumeration Requires: systemd >= 185 # For managing persistent mediated devices +# Note: for nodedev-update support at least mdevctl v1.3.0 is required Requires: mdevctl # for modprobe of pci devices Requires: module-init-tools diff --git a/src/node_device/node_device_driver.c b/src/node_device/node_device_driver.c index baff49a0ae..5bece1a30a 100644 --- a/src/node_device/node_device_driver.c +++ b/src/node_device/node_device_driver.c @@ -54,7 +54,7 @@ virNodeDeviceDriverState *driver; VIR_ENUM_IMPL(virMdevctlCommand, MDEVCTL_CMD_LAST, - "start", "stop", "define", "undefine", "create" + "start", "stop", "define", "undefine", "create", "modify" ); @@ -754,6 +754,7 @@ nodeDeviceGetMdevctlCommand(virNodeDeviceDef *def, case MDEVCTL_CMD_START: case MDEVCTL_CMD_DEFINE: case MDEVCTL_CMD_UNDEFINE: + case MDEVCTL_CMD_MODIFY: cmd = virCommandNewArgList(MDEVCTL, subcommand, NULL); break; case MDEVCTL_CMD_LAST: @@ -767,6 +768,7 @@ nodeDeviceGetMdevctlCommand(virNodeDeviceDef *def, switch (cmd_type) { case MDEVCTL_CMD_CREATE: case MDEVCTL_CMD_DEFINE: + case MDEVCTL_CMD_MODIFY: if (!def->caps->data.mdev.parent_addr) { virReportError(VIR_ERR_INTERNAL_ERROR, _("unable to find parent device '%1$s'"), def->parent); @@ -783,7 +785,8 @@ nodeDeviceGetMdevctlCommand(virNodeDeviceDef *def, virCommandAddArgPair(cmd, "--jsonfile", "/dev/stdin"); virCommandSetInputBuffer(cmd, inbuf); - virCommandSetOutputBuffer(cmd, outbuf); + if (outbuf) + virCommandSetOutputBuffer(cmd, outbuf); break; case MDEVCTL_CMD_UNDEFINE: @@ -868,6 +871,100 @@ virMdevctlDefine(virNodeDeviceDef *def, char **uuid) } +/* gets a virCommand object that executes a mdevctl command to modify a + * a device to an updated version + */ +virCommand* +nodeDeviceGetMdevctlModifyCommand(virNodeDeviceDef *def, + bool defined, + bool live, + char **errmsg) +{ + virCommand *cmd = nodeDeviceGetMdevctlCommand(def, + MDEVCTL_CMD_MODIFY, + NULL, errmsg); + + if (!cmd) + return NULL; + + if (defined) + virCommandAddArg(cmd, "--defined"); + + if (live) + virCommandAddArg(cmd, "--live"); + + return cmd; +} + + +/* checks if mdevctl supports on the command modify the options live, defined + * and jsonfile + */ +static int +nodeDeviceGetMdevctlModifySupportCheck(void) +{ + int status; + g_autoptr(virCommand) cmd = NULL; + const char *subcommand = virMdevctlCommandTypeToString(MDEVCTL_CMD_MODIFY); + + cmd = virCommandNewArgList(MDEVCTL, + subcommand, + "--defined", + "--live", + "--jsonfile", + "b", + "--help", + NULL); + + if (!cmd) + return -1; + + if (virCommandRun(cmd, &status) < 0) + return -1; + + if (status != 0) // update is unsupported + return -1; + + return 0; +} + + +static int +virMdevctlModify(virNodeDeviceDef *def, + bool defined, + bool live) +{ + int status; + g_autofree char *errmsg = NULL; + g_autoptr(virCommand) cmd = nodeDeviceGetMdevctlModifyCommand(def, + defined, + live, + &errmsg); + + if (!cmd) + return -1; + + if (nodeDeviceGetMdevctlModifySupportCheck() < 0) { + VIR_WARN("Installed mdevctl version does not support modify with options jsonfile, defined and live"); + virReportError(VIR_ERR_OPERATION_UNSUPPORTED, "%s", + _("Unable to modify mediated device: modify unsupported")); + return -1; + } + + if (virCommandRun(cmd, &status) < 0) + return -1; + + if (status != 0) { + virReportError(VIR_ERR_INTERNAL_ERROR, + _("Unable to modify mediated device: %1$s"), + MDEVCTL_ERROR(errmsg)); + return -1; + } + + return 0; +} + + static virNodeDevicePtr nodeDeviceCreateXMLMdev(virConnectPtr conn, virNodeDeviceDef *def) @@ -2106,3 +2203,158 @@ nodeDeviceIsActive(virNodeDevice *device) virNodeDeviceObjEndAPI(&obj); return ret; } + + +static int +nodeDeviceDefCloneMdevConfigs(virNodeDeviceDef *def_upd) +{ + virNodeDevCapsDef *caps = NULL; + virNodeDevCapMdev *cap_mdev_upd = NULL; + virMediatedDeviceConfig *src_mdev_config = NULL; + virMediatedDeviceConfig *dst_mdev_config = NULL; + + for (caps = def_upd->caps; caps != NULL; caps = caps->next) { + if (caps->data.type == VIR_NODE_DEV_CAP_MDEV) { + cap_mdev_upd = &caps->data.mdev; + } + } + if (!cap_mdev_upd) + return -1; + + src_mdev_config = &cap_mdev_upd->defined_config; + dst_mdev_config = &cap_mdev_upd->active_config; + + if (STRNEQ_NULLABLE(dst_mdev_config->type, src_mdev_config->type)) { + g_free(dst_mdev_config->type); + dst_mdev_config->type = g_strdup(src_mdev_config->type); + } + + virMediatedDeviceAttrsCopy(dst_mdev_config, src_mdev_config); + + return 0; +} + +
I'm making a couple additional suggestions here relevant to our discussion below.
+static int +nodeDeviceDefCompareMdevs(virNodeDeviceDef *def_cur, + virNodeDeviceDef *def_upd, + bool live)
I think this function is poorly named. It implies that you are comparing two different mdevs for equality, but it's really a function that validates whether it is valid to update from one device def to a new def. So it would be much more understandable if it was named something like nodeDeviceDefValidateUpdate().
In addition, I'd personally appreciate less cryptic variable names. For example, `def_cur` could just be `def` or `current_def`. `def_upd` could be `new_def`. Similar for the local variables below.
+{ + virNodeDevCapsDef *caps = NULL; + virNodeDevCapMdev *cap_mdev_cur = NULL; + virNodeDevCapMdev *cap_mdev_upd = NULL; + + for (caps = def_cur->caps; caps != NULL; caps = caps->next) { + if (caps->data.type == VIR_NODE_DEV_CAP_MDEV) { + cap_mdev_cur = &caps->data.mdev; + } + } + if (!cap_mdev_cur) + return -1; + + for (caps = def_upd->caps; caps != NULL; caps = caps->next) { + if (caps->data.type == VIR_NODE_DEV_CAP_MDEV) { + cap_mdev_upd = &caps->data.mdev; + } + } + if (!cap_mdev_upd) + return -1; + + if (STRNEQ_NULLABLE(cap_mdev_cur->uuid, cap_mdev_upd->uuid)) { + virReportError(VIR_ERR_CONFIG_UNSUPPORTED, + _("Cannot update device '%1$s, uuid mismatch (current uuid '%2$s')"), + def_cur->name, + cap_mdev_cur->uuid); + return -1; + } + // for a live update the types of the active configs must match!
Related to my suggestion below, you could change the comment here to something like:
/* A live update cannot change the mdev type. Since the new config is * stored in defined_config, compare that to the mdev type of the * current live config to make sure they match */
Then it would be clear why you're comparing the current active config to the new defined config.
+ if (live && + STRNEQ_NULLABLE(cap_mdev_cur->active_config.type, cap_mdev_upd->active_config.type)) { + virReportError(VIR_ERR_CONFIG_UNSUPPORTED, + _("Cannot update device '%1$s', type mismatch (current type '%2$s')"), + def_cur->name, + cap_mdev_cur->active_config.type); + return -1; + } + if (STRNEQ_NULLABLE(cap_mdev_cur->parent_addr, cap_mdev_upd->parent_addr)) { + virReportError(VIR_ERR_CONFIG_UNSUPPORTED, + _("Cannot update device '%1$s', parent address mismatch (current parent address '%2$s')"), + def_cur->name, + cap_mdev_cur->parent_addr); + return -1; + } + + return 0; +} + + +int +nodeDeviceUpdateXML(virNodeDevice *device, + const char *xmlDesc, + unsigned int flags) +{ + int ret = -1; + virNodeDeviceObj *obj = NULL; + virNodeDeviceDef *def_cur; + g_autoptr(virNodeDeviceDef) def_upd = NULL
^ another case where less cryptic variable names would be nice.
+ bool updated = false; + + virCheckFlags(VIR_NODE_DEVICE_UPDATE_XML_AFFECT_LIVE | + VIR_NODE_DEVICE_UPDATE_XML_AFFECT_CONFIG, -1); + + if (nodeDeviceInitWait() < 0) + return -1; + + if (!(obj = nodeDeviceObjFindByName(device->name))) + return -1; + def_cur = virNodeDeviceObjGetDef(obj); + + virt_type = virConnectGetType(device->conn); + + if (virNodeDeviceUpdateXMLEnsureACL(device->conn, def_cur, flags) < 0) + goto cleanup; + + if (!(def_upd = virNodeDeviceDefParse(xmlDesc, NULL, EXISTING_DEVICE, virt_type, + &driver->parserCallbacks, NULL, true))) + goto cleanup; + + if (nodeDeviceHasCapability(def_cur, VIR_NODE_DEV_CAP_MDEV) && + nodeDeviceHasCapability(def_upd, VIR_NODE_DEV_CAP_MDEV)) { + // Checks flags are valid for the state and sets flags for current if flags not set + if (virNodeDeviceObjUpdateModificationImpact(obj, &flags) < 0) + goto cleanup; + + // Before live update clone defined to active config + if (flags & VIR_NODE_DEVICE_UPDATE_XML_AFFECT_LIVE && + nodeDeviceDefCloneMdevConfigs(def_upd) < 0) + goto cleanup;
It's not clear to me why this is necessary. As far as I can tell, only the defined_config is used to generate the JSON to send to mdevctl. And this def is freed at the end of the function and then we re-query mdevctl to update our device list.
In case a live update is to be made there is a check of the mdev types also on the active config. Therefore I try to match the def used for the update to the current def by cloning the defined_config into the active_config. Another solution would have been to change the compare in nodeDeviceDefCloneMdevConfigs from STRNEQ_NULLABLE(cap_mdev_cur->active_config.type, cap_mdev_upd->active_config.type) into STRNEQ_NULLABLE(cap_mdev_cur->active_config.type, cap_mdev_upd->defined_config.type) which seems kind of wrong to me but I am willing to suffer if you disagree... :D
I assume you mean the function nodeDeviceDefCompareMdevs(). I do think that would be a better approach, actually. It does look a bit weird, but with a clear comment in the function (suggestion above), I think it's more understandable than the alternative of calling one function to copy the persistent config into the active config only for the purposes of checking it in another function.
Jonathon
Yes, I meant nodeDeviceDefCompareMdevs(). I made all the changes that you suggested above.
+ + // Compare def_cur and def_upd for compatibleness e.g. parent, type and uuid
compatibleness -> compatibility
Changed.
+ if (nodeDeviceDefCompareMdevs(def_cur, def_upd, + (flags & VIR_NODE_DEVICE_UPDATE_XML_AFFECT_LIVE)) < 0) + goto cleanup; + + // Update now + VIR_DEBUG("Update node device '%s' with mdevctl", def_cur->name); + if (virMdevctlModify(def_upd, + (flags & VIR_NODE_DEVICE_UPDATE_XML_AFFECT_CONFIG), + (flags & VIR_NODE_DEVICE_UPDATE_XML_AFFECT_LIVE)) < 0) { + goto cleanup; + }; + // Detect updates and also trigger events + updated = true; + } else { + virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s", + _("Unsupported device type")); + goto cleanup; + } + + ret = 0; + cleanup: + virNodeDeviceObjEndAPI(&obj); + if (updated) + nodeDeviceUpdateMediatedDevices(); + + return ret; +} diff --git a/src/node_device/node_device_driver.h b/src/node_device/node_device_driver.h index 4dce7e6f17..5751625eda 100644 --- a/src/node_device/node_device_driver.h +++ b/src/node_device/node_device_driver.h @@ -39,6 +39,7 @@ typedef enum { * separation makes our code more readable in terms of knowing when we're * starting a defined device and when we're creating a transient one */ MDEVCTL_CMD_CREATE, + MDEVCTL_CMD_MODIFY, MDEVCTL_CMD_LAST, } virMdevctlCommand; @@ -186,3 +187,13 @@ virCommand* nodeDeviceGetMdevctlSetAutostartCommand(virNodeDeviceDef *def, bool autostart, char **errmsg); + +virCommand* +nodeDeviceGetMdevctlModifyCommand(virNodeDeviceDef *def, + bool defined, + bool live, + char **errmsg); +int +nodeDeviceUpdateXML(virNodeDevice *dev, + const char *xmlDesc, + unsigned int flags); diff --git a/src/node_device/node_device_udev.c b/src/node_device/node_device_udev.c index 57368a96c3..4cd9d285fa 100644 --- a/src/node_device/node_device_udev.c +++ b/src/node_device/node_device_udev.c @@ -2403,6 +2403,7 @@ static virNodeDeviceDriver udevNodeDeviceDriver = { .nodeDeviceGetAutostart = nodeDeviceGetAutostart, /* 7.8.0 */ .nodeDeviceIsPersistent = nodeDeviceIsPersistent, /* 7.8.0 */ .nodeDeviceIsActive = nodeDeviceIsActive, /* 7.8.0 */ + .nodeDeviceUpdateXML = nodeDeviceUpdateXML, /* 10.1.0 */ }; diff --git a/tests/nodedevmdevctldata/mdev_c60cc60c_c60c_c60c_c60c_c60cc60cc60c_update.xml b/tests/nodedevmdevctldata/mdev_c60cc60c_c60c_c60c_c60c_c60cc60cc60c_update.xml new file mode 100644 index 0000000000..17e3611bf4 --- /dev/null +++ b/tests/nodedevmdevctldata/mdev_c60cc60c_c60c_c60c_c60c_c60cc60cc60c_update.xml @@ -0,0 +1,16 @@ +<device> + <name>mdev_c60cc60c_c60c_c60c_c60c_c60cc60cc60c_0_0_0052</name> + <path>/sys/devices/css0/0.0.0052/c60cc60c-c60c-c60c-c60c-c60cc60cc60c</path> + <parent>css_0_0_0052</parent> + <driver> + <name>vfio_ccw_mdev</name> + </driver> + <capability type='mdev'> + <type id='vfio_ccw-io'/> + <uuid>c60cc60c-c60c-c60c-c60c-c60cc60cc60c</uuid> + <parent_addr>0.0.0052</parent_addr> + <attr name='add_attr_1' value='val1'/> + <attr name='add_attr_2' value='val2'/> + <iommuGroup number='4'/> + </capability> +</device> diff --git a/tests/nodedevmdevctldata/mdevctl-modify.argv b/tests/nodedevmdevctldata/mdevctl-modify.argv new file mode 100644 index 0000000000..341c5e7fd0 --- /dev/null +++ b/tests/nodedevmdevctldata/mdevctl-modify.argv @@ -0,0 +1,25 @@ +mdevctl \ +modify \ +--parent=0.0.0052 \ +--jsonfile=/dev/stdin \ +--uuid=c60cc60c-c60c-c60c-c60c-c60cc60cc60c \ +--defined +mdevctl \ +modify \ +--parent=0.0.0052 \ +--jsonfile=/dev/stdin \ +--uuid=c60cc60c-c60c-c60c-c60c-c60cc60cc60c \ +--live +mdevctl \ +modify \ +--parent=0.0.0052 \ +--jsonfile=/dev/stdin \ +--uuid=c60cc60c-c60c-c60c-c60c-c60cc60cc60c \ +--live +mdevctl \ +modify \ +--parent=0.0.0052 \ +--jsonfile=/dev/stdin \ +--uuid=c60cc60c-c60c-c60c-c60c-c60cc60cc60c \ +--defined \ +--live diff --git a/tests/nodedevmdevctldata/mdevctl-modify.json b/tests/nodedevmdevctldata/mdevctl-modify.json new file mode 100644 index 0000000000..7ae8c73ce3 --- /dev/null +++ b/tests/nodedevmdevctldata/mdevctl-modify.json @@ -0,0 +1 @@ +{"mdev_type":"vfio_ccw-io","start":"manual"}{"mdev_type":"vfio_ccw-io","start":"manual","attrs":[{"add_attr_1":"val1"},{"add_attr_2":"val2"}]}{"mdev_type":"vfio_ccw-io","start":"manual"}{"mdev_type":"vfio_ccw-io","start":"manual","attrs":[{"add_attr_1":"val1"},{"add_attr_2":"val2"}]} diff --git a/tests/nodedevmdevctltest.c b/tests/nodedevmdevctltest.c index f49d668461..11273e3049 100644 --- a/tests/nodedevmdevctltest.c +++ b/tests/nodedevmdevctltest.c @@ -33,7 +33,10 @@ testCommandDryRunCallback(const char *const*args G_GNUC_UNUSED, { char **stdinbuf = opaque; - *stdinbuf = g_strdup(input); + if (*stdinbuf) + *stdinbuf = g_strconcat(*stdinbuf, input, NULL);
I think g_strjoin() would be nicer so we can add a newline separator between invocations.
There is no problem adding a newline seperator with the g_strconcat as well. Using g_strconcat kind of looks more natural to me
*stdinbuf = g_strconcat(*stdinbuf, "\n", input, NULL);
versus
*stdinbuf = g_strjoin("\n", *stdinbuf, input, NULL);
+ else + *stdinbuf = g_strdup(input); } typedef virCommand * (*MdevctlCmdFunc)(virNodeDeviceDef *, char **, char **); @@ -63,6 +66,7 @@ testMdevctlCmd(virMdevctlCommand cmd_type, case MDEVCTL_CMD_START: case MDEVCTL_CMD_STOP: case MDEVCTL_CMD_UNDEFINE: + case MDEVCTL_CMD_MODIFY: create = EXISTING_DEVICE; break; case MDEVCTL_CMD_LAST: @@ -173,6 +177,85 @@ testMdevctlAutostart(const void *data G_GNUC_UNUSED) return ret; } + +static int +testMdevctlModify(const void *data G_GNUC_UNUSED) +{ + g_autoptr(virNodeDeviceDef) def = NULL; + g_autoptr(virNodeDeviceDef) def_update = NULL; + virBuffer buf = VIR_BUFFER_INITIALIZER; + const char *actualCmdline = NULL; + int ret = -1; + g_autoptr(virCommand) definedcmd = NULL; + g_autoptr(virCommand) livecmd = NULL; + g_autoptr(virCommand) bothcmd = NULL; + g_autofree char *errmsg = NULL; + g_autofree char *stdinbuf = NULL; + g_autofree char *mdevxml = + g_strdup_printf("%s/nodedevschemadata/mdev_c60cc60c_c60c_c60c_c60c_c60cc60cc60c.xml", + abs_srcdir); + g_autofree char *mdevxml_update = + g_strdup_printf("%s/nodedevmdevctldata/mdev_c60cc60c_c60c_c60c_c60c_c60cc60cc60c_update.xml", + abs_srcdir); + /* just concatenate both calls into the same output files */ + g_autofree char *cmdlinefile = + g_strdup_printf("%s/nodedevmdevctldata/mdevctl-modify.argv", + abs_srcdir); + g_autofree char *jsonfile = + g_strdup_printf("%s/nodedevmdevctldata/mdevctl-modify.json", + abs_srcdir); + g_autoptr(virCommandDryRunToken) dryRunToken = virCommandDryRunTokenNew(); + + if (!(def = virNodeDeviceDefParse(NULL, mdevxml, CREATE_DEVICE, VIRT_TYPE, + &parser_callbacks, NULL, false))) + return -1; + + virCommandSetDryRun(dryRunToken, &buf, true, true, testCommandDryRunCallback, &stdinbuf); + + if (!(definedcmd = nodeDeviceGetMdevctlModifyCommand(def, true, false, &errmsg))) + goto cleanup; + + if (virCommandRun(definedcmd, NULL) < 0) + goto cleanup; + + if (!(def_update = virNodeDeviceDefParse(NULL, mdevxml_update, EXISTING_DEVICE, VIRT_TYPE, + &parser_callbacks, NULL, false))) + goto cleanup; + + if (!(livecmd = nodeDeviceGetMdevctlModifyCommand(def_update, false, true, &errmsg))) + goto cleanup; + + if (virCommandRun(livecmd, NULL) < 0) + goto cleanup; + + if (!(livecmd = nodeDeviceGetMdevctlModifyCommand(def, false, true, &errmsg))) + goto cleanup; + + if (virCommandRun(livecmd, NULL) < 0) + goto cleanup; + + if (!(bothcmd = nodeDeviceGetMdevctlModifyCommand(def_update, true, true, &errmsg))) + goto cleanup; + + if (virCommandRun(bothcmd, NULL) < 0) + goto cleanup; + + if (!(actualCmdline = virBufferCurrentContent(&buf))) + goto cleanup; + + if (virTestCompareToFileFull(actualCmdline, cmdlinefile, false) < 0) + goto cleanup; + + if (virTestCompareToFile(stdinbuf, jsonfile) < 0) + goto cleanup; + + ret = 0; + + cleanup: + virBufferFreeAndReset(&buf); + return ret; +} + static int testMdevctlListDefined(const void *data G_GNUC_UNUSED) { @@ -457,6 +540,9 @@ mymain(void) #define DO_TEST_AUTOSTART() \ DO_TEST_FULL("autostart mdevs", testMdevctlAutostart, NULL) +#define DO_TEST_MODIFY() \ + DO_TEST_FULL("modify mdevs", testMdevctlModify, NULL) + #define DO_TEST_PARSE_JSON(filename) \ DO_TEST_FULL("parse mdevctl json " filename, testMdevctlParse, filename) @@ -485,6 +571,8 @@ mymain(void) DO_TEST_AUTOSTART(); + DO_TEST_MODIFY(); + done: nodedevTestDriverFree(driver);
Reviewed-by: Jonathon Jongsma <jjongsma@redhat.com>
Thanks.
_______________________________________________ Devel mailing list -- devel@lists.libvirt.org To unsubscribe send an email to devel-leave@lists.libvirt.org
_______________________________________________ Devel mailing list -- devel@lists.libvirt.org To unsubscribe send an email to devel-leave@lists.libvirt.org
-- Mit freundlichen Grüßen/Kind regards Boris Fiuczynski IBM Deutschland Research & Development GmbH Vorsitzender des Aufsichtsrats: Wolfgang Wendt Geschäftsführung: David Faller Sitz der Gesellschaft: Böblingen Registergericht: Amtsgericht Stuttgart, HRB 243294

Add ability to update node devices. Signed-off-by: Boris Fiuczynski <fiuczy@linux.ibm.com> --- docs/manpages/virsh.rst | 19 ++++++++ tools/virsh-nodedev.c | 98 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 117 insertions(+) diff --git a/docs/manpages/virsh.rst b/docs/manpages/virsh.rst index bc0ad6542b..8224aec8c3 100644 --- a/docs/manpages/virsh.rst +++ b/docs/manpages/virsh.rst @@ -5331,6 +5331,25 @@ If *--validate* is specified, validates the format of the XML document against an internal RNG schema. +nodedev-update +-------------- + +**Syntax:** + +:: + + nodedev-update device FILE [[--live] [--config] | [--current]] + +Update a device on the host. *device* can be either device name or wwn pair +in "wwnn,wwpn" format (only works for vHBA currently). *file* +contains xml for a top-level <device> description of the node device. +*--current* can be either or both of *live* and *config*, depends on +the hypervisor's implementation. +Both *--live* and *--config* flags may be given, but *--current* is +exclusive. If no flag is specified, behavior is different depending +on hypervisor. + + nodedev-destroy --------------- diff --git a/tools/virsh-nodedev.c b/tools/virsh-nodedev.c index 5942c43689..497c380d2e 100644 --- a/tools/virsh-nodedev.c +++ b/tools/virsh-nodedev.c @@ -1299,6 +1299,98 @@ cmdNodeDeviceInfo(vshControl *ctl, const vshCmd *cmd) } +/* + * "nodedev-update" command + */ +static const vshCmdInfo info_node_device_update[] = { + {.name = "help", + .data = N_("Update a active and/or inactive node device") + }, + {.name = "desc", + .data = N_("Updates the configuration of a node device") + }, + {.name = NULL} +}; + +static const vshCmdOptDef opts_node_device_update[] = { + {.name = "device", + .type = VSH_OT_DATA, + .flags = VSH_OFLAG_REQ, + .help = N_("device name or wwn pair in 'wwnn,wwpn' format"), + .completer = virshNodeDeviceNameCompleter, + }, + VIRSH_COMMON_OPT_FILE(N_("file containing an XML description " + "of the device")), + VIRSH_COMMON_OPT_CONFIG(N_("affect next node device startup")), + VIRSH_COMMON_OPT_LIVE(N_("affect running node device")), + VIRSH_COMMON_OPT_CURRENT(N_("affect current state of node device")), + {.name = NULL} +}; + +static bool +cmdNodeDeviceUpdate(vshControl *ctl, const vshCmd *cmd) //TODO +{ + bool ret = false; + g_autoptr(virshNodeDevice) device = NULL; + const char *device_value = NULL; + const char *from = NULL; + g_autofree char *xml = NULL; + bool config = vshCommandOptBool(cmd, "config"); + bool live = vshCommandOptBool(cmd, "live"); + unsigned int flags = VIR_NODE_DEVICE_UPDATE_XML_AFFECT_CURRENT; + + VSH_EXCLUSIVE_OPTIONS("current", "live"); + VSH_EXCLUSIVE_OPTIONS("current", "config"); + + if (vshCommandOptStringReq(ctl, cmd, "device", &device_value) < 0) + return false; + + device = vshFindNodeDevice(ctl, device_value); + + if (!device) + return false; + + if (vshCommandOptStringReq(ctl, cmd, "file", &from) < 0) + goto cleanup; + + if (virFileReadAll(from, VSH_MAX_XML_FILE, &xml) < 0) + goto cleanup; + + if (config) + flags |= VIR_NETWORK_UPDATE_AFFECT_CONFIG; + if (live) + flags |= VIR_NETWORK_UPDATE_AFFECT_LIVE; + + if (virNodeDeviceUpdateXML(device, xml, flags) < 0) { + vshError(ctl, _("Failed to update node device %1$s from '%2$s'"), + virNodeDeviceGetName(device), from); + goto cleanup; + } + + if (config) { + if (live) + vshPrintExtra(ctl, _("Updated node device %1$s persistent config and live state"), + virNodeDeviceGetName(device)); + else + vshPrintExtra(ctl, _("Updated node device %1$s persistent config"), + virNodeDeviceGetName(device)); + } else if (live) { + vshPrintExtra(ctl, _("Updated node device %1$s live state"), + virNodeDeviceGetName(device)); + } else if (virNodeDeviceIsActive(device)) { + vshPrintExtra(ctl, _("Updated node device %1$s live state"), + virNodeDeviceGetName(device)); + } else { + vshPrintExtra(ctl, _("Updated node device %1$s persistent config"), + virNodeDeviceGetName(device)); + } + + ret = true; + cleanup: + vshReportError(ctl); + return ret; +} + const vshCmdDef nodedevCmds[] = { {.name = "nodedev-create", @@ -1383,5 +1475,11 @@ const vshCmdDef nodedevCmds[] = { .info = info_node_device_info, .flags = 0 }, + {.name = "nodedev-update", + .handler = cmdNodeDeviceUpdate, + .opts = opts_node_device_update, + .info = info_node_device_update, + .flags = 0 + }, {.name = NULL} }; -- 2.42.0
participants (2)
-
Boris Fiuczynski
-
Jonathon Jongsma