[libvirt PATCH v4] ch: Fix cloud-hypervisor version processing

2023-09-08 Thread Praveen K Paladugu
Refactor the version processing logic in ch driver to support versions
from non-release cloud-hypervisor binaries. This version also supports
versions with branch prefixes in them.

Signed-off-by: Praveen K Paladugu 
---
 src/ch/ch_conf.c | 34 --
 1 file changed, 32 insertions(+), 2 deletions(-)

diff --git a/src/ch/ch_conf.c b/src/ch/ch_conf.c
index a8565d9537..f421af5121 100644
--- a/src/ch/ch_conf.c
+++ b/src/ch/ch_conf.c
@@ -172,6 +172,29 @@ virCHDriverConfigDispose(void *obj)
 
 #define MIN_VERSION ((15 * 100) + (0 * 1000) + (0))
 
+/**
+ * chPreProcessVersionString:
+ *
+ * Returns: a pointer to numerical version without branch/commit info
+ */
+static char *
+chPreProcessVersionString(char *version)
+{
+char *tmp = strrchr(version, '/');
+
+if (tmp)
+version = tmp + 1;
+
+if (version[0] == 'v')
+version++;
+
+tmp = strchr(version, '-');
+if (tmp)
+*tmp = '\0';
+
+return version;
+}
+
 int
 chExtractVersion(virCHDriver *driver)
 {
@@ -193,13 +216,20 @@ chExtractVersion(virCHDriver *driver)
 
 tmp = help;
 
-/* expected format: cloud-hypervisor v.. */
-if ((tmp = STRSKIP(tmp, "cloud-hypervisor v")) == NULL) {
+/* Below are example version formats and expected outputs:
+ *  cloud-hypervisor v32.0.0 (expected: 32.0.0)
+ *  cloud-hypervisor v33.0-104-ge0e3779e-dirty (expected: 33.0)
+ *  cloud-hypervisor testing/v32.0.131-1-ga5d6db5c-dirty (expected: 
32.0.131)
+ */
+if ((tmp = STRSKIP(tmp, "cloud-hypervisor ")) == NULL) {
 virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
_("Unexpected output of cloud-hypervisor binary"));
 return -1;
 }
 
+tmp = chPreProcessVersionString(tmp);
+VIR_DEBUG("Cloud-Hypervisor version detected: %s", tmp);
+
 if (virStringParseVersion(, tmp, true) < 0) {
 virReportError(VIR_ERR_INTERNAL_ERROR,
_("Unable to parse cloud-hypervisor version: %1$s"), 
tmp);
-- 
2.41.0



Re: [libvirt PATCH 5/5] qemu: Implement support for vDPA block devices

2023-09-08 Thread Peter Krempa
On Fri, Sep 08, 2023 at 14:51:14 -0500, Jonathon Jongsma wrote:
> On 7/24/23 8:05 AM, Peter Krempa wrote:
> > As I've promised a long time ago I gave your patches some testing in
> > regards of cooperation with blockjobs and snapshots.
> > 
> > Since the new version of the patches was not yet posted on the list I'm
> > replying here including my observations from testing patches from your
> > gitlab branch:
> > 
> > On Tue, Jun 06, 2023 at 16:15:30 -0500, Jonathon Jongsma wrote:
> > > Requires recent qemu with support for the virtio-blk-vhost-vdpa device
> > > and the ability to pass a /dev/fdset/N path for the vdpa path (8.1.0)
> > > 
> > > Fixes: https://bugzilla.redhat.com/show_bug.cgi?id=1900770
> > 
> > Since this is a feature addition the 'Fixes' keyword doesn't make sense.
> > Use e.g. 'Resolves' instead.
> > 
> > Additionally you're missing the DCO certification here.
> > 
> > > ---
> > >   src/qemu/qemu_block.c  | 20 --
> > >   src/qemu/qemu_domain.c | 25 
> > >   src/qemu/qemu_validate.c   | 44 +++---
> > >   tests/qemuxml2argvdata/disk-vhostvdpa.args | 35 +
> > >   tests/qemuxml2argvdata/disk-vhostvdpa.xml  | 21 +++
> > >   tests/qemuxml2argvtest.c   |  2 +
> > >   6 files changed, 139 insertions(+), 8 deletions(-)
> > >   create mode 100644 tests/qemuxml2argvdata/disk-vhostvdpa.args
> > >   create mode 100644 tests/qemuxml2argvdata/disk-vhostvdpa.xml
> > 
> > [...]
> > 
> > > diff --git a/src/qemu/qemu_domain.c b/src/qemu/qemu_domain.c
> > > index 2f6b32e394..119e52a7d7 100644
> > > --- a/src/qemu/qemu_domain.c
> > > +++ b/src/qemu/qemu_domain.c
> > > @@ -11157,6 +11157,28 @@ 
> > > qemuDomainPrepareStorageSourceFDs(virStorageSource *src,
> > >   }
> > > +static int
> > > +qemuDomainPrepareStorageSourceVDPA(virStorageSource *src,
> > > +   qemuDomainObjPrivate *priv)
> > > +{
> > > +qemuDomainStorageSourcePrivate *srcpriv = NULL;
> > > +virStorageType actualType = virStorageSourceGetActualType(src);
> > > +int vdpafd = -1;
> > > +
> > > +if (actualType != VIR_STORAGE_TYPE_VHOST_VDPA)
> > > +return 0;
> > > +
> > > +if ((vdpafd = qemuVDPAConnect(src->path)) < 0)
> > > +return -1;
> > 
> > This function call directly touches the host filesystem, which is not
> > supposed to be in the *DomainPrepareStorageSource* functions but we
> > rather have a completely separate machinery in
> > qemuProcessPrepareHostStorage.
> > 
> > Unfortunately that one doesn't yet need to handle individual backing
> > chain members though.
> > 
> > This ensures that the code doesn't get accidentally called from tests
> > even without mocking the code as the tests reimplement the functions
> > differently for testing purposes.
> 
> Somehow I missed this comment earlier. Unfortunately, it doesn't seem
> straightforward to move this code. We can't simply move all of the logic to
> qemuProcessPrepareHostStorage() because that function doesn't get called
> during the tests.

That is the exact idea of it. This must not be called from tests because
you must not attempt to rely on host state during tests.

To make tests work, in testCompareXMLToArgvCreateArgs, we then setup a
fake FD to be passed to the command line generator. Similarly other
stuff is mocked there, mostly FD passing-related.

Additionally qemuProcessPrepareHostStorage() is also skipped inside the
domxmlToNative API as you also don't want to actually attempt opening
the VDPA socket in case when it won't be used. In that case mocking via
LD_PRELOAD is not possible.

In certain cases (if we care enough about the domxml->native API) we
even setup an alternative syntax (e.g. not using FD passing) so that the
users can adapt and use such commandline as inspiration to actually run
qemu.

> I thought I could move only the opening of the fd to the
> PrepareHostStorage() function and then keep the qemuFDPass construction in
> this function, but that doesn't work: the PrepareHostStorage() function
> actually gets called *after* this function. So the fd would not even be open
> yet at the time this function gets called.
> 
> So... it seems that the options are either:
> 
> - leave everything in qemuDomainPrepareStorageSourceVDPA() (as is)

No. That would violate the idea of qemuProcessPrepareHostStorage().

> - move the fd opening to PrepareHostStorage() and then move the rest to a
> different common function that is called after that, such as
> qemuBuildDiskSourceCommandLine()

Inside PrepareHostStorage() you both open the FD and create the fdpass
structure and assign it to the private data, so there's no need for any
other location to do anything else.

> - a third option (suggestions?)
> 
> It's worth noting that the vdpa *network* device essentially does everything
> (opening the file, creating the qemuFDPass object, etc) in the
> qemuBuildInterfaceCommandLine() function. 

Re: [libvirt PATCH 5/5] qemu: Implement support for vDPA block devices

2023-09-08 Thread Jonathon Jongsma

On 7/24/23 8:05 AM, Peter Krempa wrote:

As I've promised a long time ago I gave your patches some testing in
regards of cooperation with blockjobs and snapshots.

Since the new version of the patches was not yet posted on the list I'm
replying here including my observations from testing patches from your
gitlab branch:

On Tue, Jun 06, 2023 at 16:15:30 -0500, Jonathon Jongsma wrote:

Requires recent qemu with support for the virtio-blk-vhost-vdpa device
and the ability to pass a /dev/fdset/N path for the vdpa path (8.1.0)

Fixes: https://bugzilla.redhat.com/show_bug.cgi?id=1900770


Since this is a feature addition the 'Fixes' keyword doesn't make sense.
Use e.g. 'Resolves' instead.

Additionally you're missing the DCO certification here.


---
  src/qemu/qemu_block.c  | 20 --
  src/qemu/qemu_domain.c | 25 
  src/qemu/qemu_validate.c   | 44 +++---
  tests/qemuxml2argvdata/disk-vhostvdpa.args | 35 +
  tests/qemuxml2argvdata/disk-vhostvdpa.xml  | 21 +++
  tests/qemuxml2argvtest.c   |  2 +
  6 files changed, 139 insertions(+), 8 deletions(-)
  create mode 100644 tests/qemuxml2argvdata/disk-vhostvdpa.args
  create mode 100644 tests/qemuxml2argvdata/disk-vhostvdpa.xml


[...]


diff --git a/src/qemu/qemu_domain.c b/src/qemu/qemu_domain.c
index 2f6b32e394..119e52a7d7 100644
--- a/src/qemu/qemu_domain.c
+++ b/src/qemu/qemu_domain.c
@@ -11157,6 +11157,28 @@ qemuDomainPrepareStorageSourceFDs(virStorageSource 
*src,
  }
  
  
+static int

+qemuDomainPrepareStorageSourceVDPA(virStorageSource *src,
+   qemuDomainObjPrivate *priv)
+{
+qemuDomainStorageSourcePrivate *srcpriv = NULL;
+virStorageType actualType = virStorageSourceGetActualType(src);
+int vdpafd = -1;
+
+if (actualType != VIR_STORAGE_TYPE_VHOST_VDPA)
+return 0;
+
+if ((vdpafd = qemuVDPAConnect(src->path)) < 0)
+return -1;


This function call directly touches the host filesystem, which is not
supposed to be in the *DomainPrepareStorageSource* functions but we
rather have a completely separate machinery in
qemuProcessPrepareHostStorage.

Unfortunately that one doesn't yet need to handle individual backing
chain members though.

This ensures that the code doesn't get accidentally called from tests
even without mocking the code as the tests reimplement the functions
differently for testing purposes.


Somehow I missed this comment earlier. Unfortunately, it doesn't seem 
straightforward to move this code. We can't simply move all of the logic 
to qemuProcessPrepareHostStorage() because that function doesn't get 
called during the tests. I thought I could move only the opening of the 
fd to the PrepareHostStorage() function and then keep the qemuFDPass 
construction in this function, but that doesn't work: the 
PrepareHostStorage() function actually gets called *after* this 
function. So the fd would not even be open yet at the time this function 
gets called.


So... it seems that the options are either:

- leave everything in qemuDomainPrepareStorageSourceVDPA() (as is)
- move the fd opening to PrepareHostStorage() and then move the rest to 
a different common function that is called after that, such as 
qemuBuildDiskSourceCommandLine()

- a third option (suggestions?)

It's worth noting that the vdpa *network* device essentially does 
everything (opening the file, creating the qemuFDPass object, etc) in 
the qemuBuildInterfaceCommandLine() function. This was done to match 
other network devices that connect to open file descriptors (see 
qemuBuildInterfaceConnect()). But based on your comments above, it 
sounds like this may not be a the ideal situation even though the 
function is mocked to not actually open any file descriptors from the 
host filesystem under test.


Jonathon




+
+srcpriv = qemuDomainStorageSourcePrivateFetch(src);
+
+srcpriv->fdpass = qemuFDPassNew(src->nodestorage, priv);
+qemuFDPassAddFD(srcpriv->fdpass, , "-vdpa");
+return 0;
+}


[...]


diff --git a/src/qemu/qemu_validate.c b/src/qemu/qemu_validate.c
index 9dce908cfe..67b0944162 100644
--- a/src/qemu/qemu_validate.c
+++ b/src/qemu/qemu_validate.c
@@ -3220,6 +3220,28 @@ qemuValidateDomainDeviceDefDiskTransient(const 
virDomainDiskDef *disk,
  }
  
  
+static int

+qemuValidateDomainDeviceDefDiskVhost(const virDomainDef *def,
+ virStorageType storagetype,
+ virQEMUCapsFlags flag,
+ virQEMUCaps *qemuCaps)
+{
+const char *vhosttype = virStorageTypeToString(storagetype);
+
+if (!virQEMUCapsGet(qemuCaps, flag)) {
+virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
+   _("%1$s disk is not supported with this QEMU binary"),
+   vhosttype);
+return -1;


I'd prefer if both things this function does are duplicated 

[PATCH v2] interface: fix udev_device_get_sysattr_value return value check

2023-09-08 Thread Dmitry Frolov
Reviewing the code I found that return value of function
udev_device_get_sysattr_value() is dereferenced without a check.
udev_device_get_sysattr_value() may return NULL by number of reasons.

v2: VIR_DEBUG added, replaced STREQ(NULLSTR()) with STREQ_NULLABLE()

Signed-off-by: Dmitry Frolov 
---
 src/interface/interface_backend_udev.c | 16 +++-
 1 file changed, 11 insertions(+), 5 deletions(-)

diff --git a/src/interface/interface_backend_udev.c 
b/src/interface/interface_backend_udev.c
index a0485ddd21..df7066727e 100644
--- a/src/interface/interface_backend_udev.c
+++ b/src/interface/interface_backend_udev.c
@@ -23,6 +23,7 @@
 #include 
 #include 
 
+#include "virlog.h"
 #include "virerror.h"
 #include "virfile.h"
 #include "datatypes.h"
@@ -40,6 +41,8 @@
 
 #define VIR_FROM_THIS VIR_FROM_INTERFACE
 
+VIR_LOG_INIT("interface.interface_backend_udev");
+
 struct udev_iface_driver {
 struct udev *udev;
 /* pid file FD, ensures two copies of the driver can't use the same root */
@@ -355,10 +358,13 @@ udevConnectListAllInterfaces(virConnectPtr conn,
 g_autoptr(virInterfaceDef) def = NULL;
 
 path = udev_list_entry_get_name(dev_entry);
-dev = udev_device_new_from_syspath(udev, path);
+if (!(dev = udev_device_new_from_syspath(udev, path))) {
+VIR_DEBUG("Skipping interface '%s'", NULLSTR(path));
+continue;
+}
 name = udev_device_get_sysname(dev);
 macaddr = udev_device_get_sysattr_value(dev, "address");
-status = STREQ(udev_device_get_sysattr_value(dev, "operstate"), "up");
+status = STREQ_NULLABLE(udev_device_get_sysattr_value(dev, 
"operstate"), "up");
 
 def = udevGetMinimalDefForDevice(dev);
 if (!virConnectListAllInterfacesCheckACL(conn, def)) {
@@ -964,9 +970,9 @@ udevGetIfaceDef(struct udev *udev, const char *name)
 
 /* MTU */
 mtu_str = udev_device_get_sysattr_value(dev, "mtu");
-if (virStrToLong_ui(mtu_str, NULL, 10, ) < 0) {
+if (!mtu_str || virStrToLong_ui(mtu_str, NULL, 10, ) < 0) {
 virReportError(VIR_ERR_INTERNAL_ERROR,
-_("Could not parse MTU value '%1$s'"), mtu_str);
+_("Could not parse MTU value '%1$s'"), NULLSTR(mtu_str));
 goto error;
 }
 ifacedef->mtu = mtu;
@@ -1089,7 +1095,7 @@ udevInterfaceIsActive(virInterfacePtr ifinfo)
goto cleanup;
 
 /* Check if it's active or not */
-status = STREQ(udev_device_get_sysattr_value(dev, "operstate"), "up");
+status = STREQ_NULLABLE(udev_device_get_sysattr_value(dev, "operstate"), 
"up");
 
 udev_device_unref(dev);
 
-- 
2.34.1



Re: [PATCH] interface: fix udev_device_get_sysattr_value return value check

2023-09-08 Thread Peter Krempa
On Fri, Sep 08, 2023 at 13:06:43 +0300, Dmitry Frolov wrote:
> Reviewing the code I found that return value of function
> udev_device_get_sysattr_value() is dereferenced without a check.
> udev_device_get_sysattr_value() may return NULL by number of reasons.
> 
> Signed-off-by: Dmitry Frolov 
> ---
>  src/interface/interface_backend_udev.c | 11 ++-
>  1 file changed, 6 insertions(+), 5 deletions(-)
> 
> diff --git a/src/interface/interface_backend_udev.c 
> b/src/interface/interface_backend_udev.c
> index a0485ddd21..c820b3ccdf 100644
> --- a/src/interface/interface_backend_udev.c
> +++ b/src/interface/interface_backend_udev.c
> @@ -355,10 +355,11 @@ udevConnectListAllInterfaces(virConnectPtr conn,
>  g_autoptr(virInterfaceDef) def = NULL;
>  
>  path = udev_list_entry_get_name(dev_entry);
> -dev = udev_device_new_from_syspath(udev, path);
> +if (!(dev = udev_device_new_from_syspath(udev, path)))
> +continue;

IMO this warants at least a VIR_DEBUG stating that the returned
'dev' struct is NULL and thus we're skipping a whole interface.

>  name = udev_device_get_sysname(dev);

This is also documented as returning NULL on error, but virGetInterface
actually checks it before use.

>  macaddr = udev_device_get_sysattr_value(dev, "address");
> -status = STREQ(udev_device_get_sysattr_value(dev, "operstate"), 
> "up");
> +status = STREQ(NULLSTR(udev_device_get_sysattr_value(dev, 
> "operstate")), "up");

For comparing strings which can be NULL we have STREQ_NULLABLE and
STRNEQ_NULLABLE.



[PATCH] interface: fix udev_device_get_sysattr_value return value check

2023-09-08 Thread Dmitry Frolov
Reviewing the code I found that return value of function
udev_device_get_sysattr_value() is dereferenced without a check.
udev_device_get_sysattr_value() may return NULL by number of reasons.

Signed-off-by: Dmitry Frolov 
---
 src/interface/interface_backend_udev.c | 11 ++-
 1 file changed, 6 insertions(+), 5 deletions(-)

diff --git a/src/interface/interface_backend_udev.c 
b/src/interface/interface_backend_udev.c
index a0485ddd21..c820b3ccdf 100644
--- a/src/interface/interface_backend_udev.c
+++ b/src/interface/interface_backend_udev.c
@@ -355,10 +355,11 @@ udevConnectListAllInterfaces(virConnectPtr conn,
 g_autoptr(virInterfaceDef) def = NULL;
 
 path = udev_list_entry_get_name(dev_entry);
-dev = udev_device_new_from_syspath(udev, path);
+if (!(dev = udev_device_new_from_syspath(udev, path)))
+continue;
 name = udev_device_get_sysname(dev);
 macaddr = udev_device_get_sysattr_value(dev, "address");
-status = STREQ(udev_device_get_sysattr_value(dev, "operstate"), "up");
+status = STREQ(NULLSTR(udev_device_get_sysattr_value(dev, 
"operstate")), "up");
 
 def = udevGetMinimalDefForDevice(dev);
 if (!virConnectListAllInterfacesCheckACL(conn, def)) {
@@ -964,9 +965,9 @@ udevGetIfaceDef(struct udev *udev, const char *name)
 
 /* MTU */
 mtu_str = udev_device_get_sysattr_value(dev, "mtu");
-if (virStrToLong_ui(mtu_str, NULL, 10, ) < 0) {
+if (!mtu_str || virStrToLong_ui(mtu_str, NULL, 10, ) < 0) {
 virReportError(VIR_ERR_INTERNAL_ERROR,
-_("Could not parse MTU value '%1$s'"), mtu_str);
+_("Could not parse MTU value '%1$s'"), NULLSTR(mtu_str));
 goto error;
 }
 ifacedef->mtu = mtu;
@@ -1089,7 +1090,7 @@ udevInterfaceIsActive(virInterfacePtr ifinfo)
goto cleanup;
 
 /* Check if it's active or not */
-status = STREQ(udev_device_get_sysattr_value(dev, "operstate"), "up");
+status = STREQ(NULLSTR(udev_device_get_sysattr_value(dev, "operstate")), 
"up");
 
 udev_device_unref(dev);
 
-- 
2.34.1



Re: [PATCH] virnetdevopenvswitch: Propagate OVS error messages

2023-09-08 Thread Martin Kletzander

On Fri, Sep 08, 2023 at 11:04:38AM +0200, Michal Privoznik wrote:

When configuring OVS interfaces/bridges we spawn 'ovs-vsctl' with
appropriate arguments and if it exited with a non-zero status we
report a generic error message, like "Unable to add port vnet0 to
OVS bridge ovsbr0". This is all cool, but the real reason why
operation failed is hidden in (debug) logs because that's where
virCommandRun() reports it unless caller requested otherwise.

This is a bit clumsy because then we have to ask users to turn on
debug logs and reproduce the problem again, e.g. [1].

Therefore, in cases where an error is reported to the user - just
read ovs-vsctl's stderr and include it in the error message. For
other cases (like VIR_DEBUG/VIR_WARN) - well they are meant to
end up in (debug) logs anyway.

1: https://mail.openvswitch.org/pipermail/ovs-discuss/2023-September/052640.html
Signed-off-by: Michal Privoznik 


Reviewed-by: Martin Kletzander 


---
src/util/virnetdevopenvswitch.c | 93 -
1 file changed, 58 insertions(+), 35 deletions(-)

diff --git a/src/util/virnetdevopenvswitch.c b/src/util/virnetdevopenvswitch.c
index 8dad6ed2bd..d836d05845 100644
--- a/src/util/virnetdevopenvswitch.c
+++ b/src/util/virnetdevopenvswitch.c
@@ -54,10 +54,14 @@ virNetDevOpenvswitchSetTimeout(unsigned int timeout)
}

static virCommand *
-virNetDevOpenvswitchCreateCmd(void)
+virNetDevOpenvswitchCreateCmd(char **errbuf)
{
virCommand *cmd = virCommandNew(OVS_VSCTL);
+
virCommandAddArgFormat(cmd, "--timeout=%u", virNetDevOpenvswitchTimeout);
+if (errbuf)
+virCommandSetErrorBuffer(cmd, errbuf);
+
return cmd;
}

@@ -137,6 +141,7 @@ int virNetDevOpenvswitchAddPort(const char *brname, const 
char *ifname,
char ifuuidstr[VIR_UUID_STRING_BUFLEN];
char vmuuidstr[VIR_UUID_STRING_BUFLEN];
g_autoptr(virCommand) cmd = NULL;
+g_autofree char *errbuf = NULL;
g_autofree char *attachedmac_ex_id = NULL;
g_autofree char *ifaceid_ex_id = NULL;
g_autofree char *profile_ex_id = NULL;
@@ -157,7 +162,7 @@ int virNetDevOpenvswitchAddPort(const char *brname, const 
char *ifname,
ovsport->profileID);
}

-cmd = virNetDevOpenvswitchCreateCmd();
+cmd = virNetDevOpenvswitchCreateCmd();
virCommandAddArgList(cmd, "--", "--may-exist",
 "add-port", brname, ifname, NULL);

@@ -185,8 +190,8 @@ int virNetDevOpenvswitchAddPort(const char *brname, const 
char *ifname,

if (virCommandRun(cmd, NULL) < 0) {
virReportError(VIR_ERR_INTERNAL_ERROR,
-   _("Unable to add port %1$s to OVS bridge %2$s"),
-   ifname, brname);
+   _("Unable to add port %1$s to OVS bridge %2$s: %3$s"),
+   ifname, brname, NULLSTR(errbuf));
return -1;
}

@@ -203,13 +208,15 @@ int virNetDevOpenvswitchAddPort(const char *brname, const 
char *ifname,
 */
int virNetDevOpenvswitchRemovePort(const char *brname G_GNUC_UNUSED, const char 
*ifname)
{
-g_autoptr(virCommand) cmd = virNetDevOpenvswitchCreateCmd();
+g_autofree char *errbuf = NULL;
+g_autoptr(virCommand) cmd = virNetDevOpenvswitchCreateCmd();

virCommandAddArgList(cmd, "--", "--if-exists", "del-port", ifname, NULL);

if (virCommandRun(cmd, NULL) < 0) {
virReportError(VIR_ERR_INTERNAL_ERROR,
-   _("Unable to delete port %1$s from OVS"), ifname);
+   _("Unable to delete port %1$s from OVS: %2$s"),
+   ifname, NULLSTR(errbuf));
return -1;
}

@@ -228,7 +235,8 @@ int virNetDevOpenvswitchRemovePort(const char *brname 
G_GNUC_UNUSED, const char
int virNetDevOpenvswitchGetMigrateData(char **migrate, const char *ifname)
{
size_t len;
-g_autoptr(virCommand) cmd = virNetDevOpenvswitchCreateCmd();
+g_autofree char *errbuf = NULL;
+g_autoptr(virCommand) cmd = virNetDevOpenvswitchCreateCmd();

virCommandAddArgList(cmd, "--if-exists", "get", "Interface",
 ifname, "external_ids:PortData", NULL);
@@ -238,8 +246,8 @@ int virNetDevOpenvswitchGetMigrateData(char **migrate, 
const char *ifname)
/* Run the command */
if (virCommandRun(cmd, NULL) < 0) {
virReportError(VIR_ERR_INTERNAL_ERROR,
-   _("Unable to run command to get OVS port data for interface 
%1$s"),
-   ifname);
+   _("Unable to run command to get OVS port data for interface 
%1$s: %2$s"),
+   ifname, NULLSTR(errbuf));
return -1;
}

@@ -263,21 +271,22 @@ int virNetDevOpenvswitchGetMigrateData(char **migrate, 
const char *ifname)
int virNetDevOpenvswitchSetMigrateData(char *migrate, const char *ifname)
{
g_autoptr(virCommand) cmd = NULL;
+g_autofree char *errbuf = NULL;

if (!migrate) {
VIR_DEBUG("No OVS port data for interface %s", ifname);
return 0;
}

-cmd = 

[PATCH] virnetdevopenvswitch: Propagate OVS error messages

2023-09-08 Thread Michal Privoznik
When configuring OVS interfaces/bridges we spawn 'ovs-vsctl' with
appropriate arguments and if it exited with a non-zero status we
report a generic error message, like "Unable to add port vnet0 to
OVS bridge ovsbr0". This is all cool, but the real reason why
operation failed is hidden in (debug) logs because that's where
virCommandRun() reports it unless caller requested otherwise.

This is a bit clumsy because then we have to ask users to turn on
debug logs and reproduce the problem again, e.g. [1].

Therefore, in cases where an error is reported to the user - just
read ovs-vsctl's stderr and include it in the error message. For
other cases (like VIR_DEBUG/VIR_WARN) - well they are meant to
end up in (debug) logs anyway.

1: https://mail.openvswitch.org/pipermail/ovs-discuss/2023-September/052640.html
Signed-off-by: Michal Privoznik 
---
 src/util/virnetdevopenvswitch.c | 93 -
 1 file changed, 58 insertions(+), 35 deletions(-)

diff --git a/src/util/virnetdevopenvswitch.c b/src/util/virnetdevopenvswitch.c
index 8dad6ed2bd..d836d05845 100644
--- a/src/util/virnetdevopenvswitch.c
+++ b/src/util/virnetdevopenvswitch.c
@@ -54,10 +54,14 @@ virNetDevOpenvswitchSetTimeout(unsigned int timeout)
 }
 
 static virCommand *
-virNetDevOpenvswitchCreateCmd(void)
+virNetDevOpenvswitchCreateCmd(char **errbuf)
 {
 virCommand *cmd = virCommandNew(OVS_VSCTL);
+
 virCommandAddArgFormat(cmd, "--timeout=%u", virNetDevOpenvswitchTimeout);
+if (errbuf)
+virCommandSetErrorBuffer(cmd, errbuf);
+
 return cmd;
 }
 
@@ -137,6 +141,7 @@ int virNetDevOpenvswitchAddPort(const char *brname, const 
char *ifname,
 char ifuuidstr[VIR_UUID_STRING_BUFLEN];
 char vmuuidstr[VIR_UUID_STRING_BUFLEN];
 g_autoptr(virCommand) cmd = NULL;
+g_autofree char *errbuf = NULL;
 g_autofree char *attachedmac_ex_id = NULL;
 g_autofree char *ifaceid_ex_id = NULL;
 g_autofree char *profile_ex_id = NULL;
@@ -157,7 +162,7 @@ int virNetDevOpenvswitchAddPort(const char *brname, const 
char *ifname,
 ovsport->profileID);
 }
 
-cmd = virNetDevOpenvswitchCreateCmd();
+cmd = virNetDevOpenvswitchCreateCmd();
 virCommandAddArgList(cmd, "--", "--may-exist",
  "add-port", brname, ifname, NULL);
 
@@ -185,8 +190,8 @@ int virNetDevOpenvswitchAddPort(const char *brname, const 
char *ifname,
 
 if (virCommandRun(cmd, NULL) < 0) {
 virReportError(VIR_ERR_INTERNAL_ERROR,
-   _("Unable to add port %1$s to OVS bridge %2$s"),
-   ifname, brname);
+   _("Unable to add port %1$s to OVS bridge %2$s: %3$s"),
+   ifname, brname, NULLSTR(errbuf));
 return -1;
 }
 
@@ -203,13 +208,15 @@ int virNetDevOpenvswitchAddPort(const char *brname, const 
char *ifname,
  */
 int virNetDevOpenvswitchRemovePort(const char *brname G_GNUC_UNUSED, const 
char *ifname)
 {
-g_autoptr(virCommand) cmd = virNetDevOpenvswitchCreateCmd();
+g_autofree char *errbuf = NULL;
+g_autoptr(virCommand) cmd = virNetDevOpenvswitchCreateCmd();
 
 virCommandAddArgList(cmd, "--", "--if-exists", "del-port", ifname, NULL);
 
 if (virCommandRun(cmd, NULL) < 0) {
 virReportError(VIR_ERR_INTERNAL_ERROR,
-   _("Unable to delete port %1$s from OVS"), ifname);
+   _("Unable to delete port %1$s from OVS: %2$s"),
+   ifname, NULLSTR(errbuf));
 return -1;
 }
 
@@ -228,7 +235,8 @@ int virNetDevOpenvswitchRemovePort(const char *brname 
G_GNUC_UNUSED, const char
 int virNetDevOpenvswitchGetMigrateData(char **migrate, const char *ifname)
 {
 size_t len;
-g_autoptr(virCommand) cmd = virNetDevOpenvswitchCreateCmd();
+g_autofree char *errbuf = NULL;
+g_autoptr(virCommand) cmd = virNetDevOpenvswitchCreateCmd();
 
 virCommandAddArgList(cmd, "--if-exists", "get", "Interface",
  ifname, "external_ids:PortData", NULL);
@@ -238,8 +246,8 @@ int virNetDevOpenvswitchGetMigrateData(char **migrate, 
const char *ifname)
 /* Run the command */
 if (virCommandRun(cmd, NULL) < 0) {
 virReportError(VIR_ERR_INTERNAL_ERROR,
-   _("Unable to run command to get OVS port data for 
interface %1$s"),
-   ifname);
+   _("Unable to run command to get OVS port data for 
interface %1$s: %2$s"),
+   ifname, NULLSTR(errbuf));
 return -1;
 }
 
@@ -263,21 +271,22 @@ int virNetDevOpenvswitchGetMigrateData(char **migrate, 
const char *ifname)
 int virNetDevOpenvswitchSetMigrateData(char *migrate, const char *ifname)
 {
 g_autoptr(virCommand) cmd = NULL;
+g_autofree char *errbuf = NULL;
 
 if (!migrate) {
 VIR_DEBUG("No OVS port data for interface %s", ifname);
 return 0;
 }
 
-cmd = virNetDevOpenvswitchCreateCmd();
+cmd =