[libvirt PATCH v3 00/18] Use nbdkit for http/ftp/ssh network drives in libvirt

This is the third version of this patch series. See https://bugzilla.redhat.com/show_bug.cgi?id=2016527 for more information about the goal, but the summary is that RHEL does not want to ship the qemu storage plugins for curl and ssh. Handling them outside of the qemu process provides several advantages such as reduced attack surface and stability. A quick summary of the code: - at startup I query to see whether nbdkit exists on the host and if so, I query which plugins/filters are installed. These capabilities are cached and stored in the qemu driver - When the driver prepares the domain, we go through each disk source and determine whether the nbdkit capabilities allow us to support this disk via nbdkit, and if so, we allocate a qemuNbdkitProcess object and stash it in the private data of the virStorageSource. - The presence or absence of this qemuNbdkitProcess data then indicates whether this disk will be served to qemu indirectly via nbdkit or directly - When we launch the qemuProcess, as part of the "external device start" step, I launch a ndkit process for each disk that is supported by nbdkit. - for devices which are served by an intermediate ndkit process, I change the qemu commandline in the following ways: - I no longer pass auth/cookie secrets to qemu (those are handled by nbdkit) - I replace the actual network URL of the remote disk source with the path to the nbdkit unix socket - We create a 'monitor' for the nbdkit process that watches to see whether the process exits. If it does, we pause the domain, attempt to restart nbdkit, and then resume the domain. Open questions - I think selinux will work once we add a policy for the /usr/sbin/nbdkit binary to allow it to be executed by libvirt, but for now it fails to execute nbdkit in enforcing mode. The current context for nbdkit (on fedora) is "system_u:object_r:bin_t:s0". When I temporarily change the context to something like qemu_exec_t, I am able to start nbdkit and the domain launches. Known shortcomings - creating disks (in ssh) still isn't supported. Changes in v3: - Various formatting fixes - Don't kill process in qemuNbdkitProcessFree() since we want the nbdkit daemon to continue running even if libvirt restarts. - Better detection and error reporting when starting the nbdkit process - Add monitoring for nbdkit process so that it can be restarted if if ever exits unexpectedly. Changes in v2: - split into multiple patches - added a build option for nbdkit_moddir - don't instantiate any secret / cookie props for disks that are being served by nbdkit since we don't send secrets to qemu anymore - ensure that nbdkit processes are started/stopped for the entire backing chain - switch to virFileCache-based capabilities for nbdkit so that we don't need to requery every time - switch to using pipes for communicating sensitive data to nbdkit - use pidfile support built into virCommand rather than nbdkit's --pidfile argument - added significantly more tests Jonathon Jongsma (18): schema: allow 'ssh' as a protocol for network disks qemu: Add functions for determining nbdkit availability qemu: expand nbdkit capabilities util: Allow virFileCache data to be any GObject qemu: implement basic virFileCache for nbdkit caps qemu: implement persistent file cache for nbdkit caps qemu: use file cache for nbdkit caps qemu: Add qemuNbdkitProcess qemu: add functions to start and stop nbdkit tests: add ability to test various nbdkit capabilities qemu: split qemuDomainSecretStorageSourcePrepare qemu: include nbdkit state in private xml qemu: use nbdkit to serve network disks if available tests: add tests for nbdkit invocation util: make virCommandSetSendBuffer testable qemu: pass sensitive data to nbdkit via pipe qemu: add test for authenticating a https network disk qemu: Monitor nbdkit process for exit build-aux/syntax-check.mk | 4 +- meson.build | 9 + meson_options.txt | 1 + po/POTFILES | 1 + src/conf/schemas/domaincommon.rng | 1 + src/libvirt_private.syms | 1 + src/qemu/meson.build | 1 + src/qemu/qemu_block.c | 162 ++- src/qemu/qemu_conf.c | 23 + src/qemu/qemu_conf.h | 7 + src/qemu/qemu_domain.c | 180 ++- src/qemu/qemu_domain.h | 4 + src/qemu/qemu_driver.c | 4 + src/qemu/qemu_extdevice.c | 48 + src/qemu/qemu_nbdkit.c | 1243 +++++++++++++++++ src/qemu/qemu_nbdkit.h | 120 ++ src/qemu/qemu_nbdkitpriv.h | 31 + src/qemu/qemu_process.c | 13 + src/util/vircommand.c | 19 +- src/util/vircommand.h | 8 + src/util/vircommandpriv.h | 4 + src/util/virfilecache.c | 14 +- src/util/virfilecache.h | 2 +- src/util/virutil.h | 2 +- tests/meson.build | 1 + .../disk-cdrom-network.args.disk0 | 7 + .../disk-cdrom-network.args.disk1 | 9 + .../disk-cdrom-network.args.disk1.pipe.1778 | 1 + .../disk-cdrom-network.args.disk2 | 9 + .../disk-cdrom-network.args.disk2.pipe.1780 | 1 + .../disk-network-http.args.disk0 | 7 + .../disk-network-http.args.disk1 | 6 + .../disk-network-http.args.disk2 | 7 + .../disk-network-http.args.disk2.pipe.1778 | 1 + .../disk-network-http.args.disk3 | 8 + .../disk-network-http.args.disk3.pipe.1780 | 1 + ...work-source-curl-nbdkit-backing.args.disk0 | 8 + ...e-curl-nbdkit-backing.args.disk0.pipe.1778 | 1 + .../disk-network-source-curl.args.1.pipe.1 | 1 + .../disk-network-source-curl.args.disk0 | 8 + ...k-network-source-curl.args.disk0.pipe.1778 | 1 + .../disk-network-source-curl.args.disk1 | 10 + ...k-network-source-curl.args.disk1.pipe.1780 | 1 + ...k-network-source-curl.args.disk1.pipe.1782 | 1 + ...isk-network-source-curl.args.disk1.pipe.49 | 1 + .../disk-network-source-curl.args.disk2 | 8 + ...k-network-source-curl.args.disk2.pipe.1782 | 1 + ...k-network-source-curl.args.disk2.pipe.1784 | 1 + ...isk-network-source-curl.args.disk2.pipe.51 | 1 + .../disk-network-source-curl.args.disk3 | 7 + .../disk-network-source-curl.args.disk4 | 7 + .../disk-network-ssh.args.disk0 | 7 + tests/qemunbdkittest.c | 294 ++++ ...sk-cdrom-network-nbdkit.x86_64-latest.args | 42 + .../disk-cdrom-network-nbdkit.xml | 1 + ...isk-network-http-nbdkit.x86_64-latest.args | 45 + .../disk-network-http-nbdkit.xml | 1 + ...rce-curl-nbdkit-backing.x86_64-latest.args | 38 + ...isk-network-source-curl-nbdkit-backing.xml | 45 + ...work-source-curl-nbdkit.x86_64-latest.args | 50 + .../disk-network-source-curl-nbdkit.xml | 1 + ...isk-network-source-curl.x86_64-latest.args | 54 + .../disk-network-source-curl.xml | 74 + ...disk-network-ssh-nbdkit.x86_64-latest.args | 36 + .../disk-network-ssh-nbdkit.xml | 1 + .../disk-network-ssh.x86_64-latest.args | 36 + tests/qemuxml2argvdata/disk-network-ssh.xml | 31 + tests/qemuxml2argvtest.c | 18 + tests/testutilsqemu.c | 27 + tests/testutilsqemu.h | 5 + 70 files changed, 2707 insertions(+), 116 deletions(-) create mode 100644 src/qemu/qemu_nbdkit.c create mode 100644 src/qemu/qemu_nbdkit.h create mode 100644 src/qemu/qemu_nbdkitpriv.h create mode 100644 tests/qemunbdkitdata/disk-cdrom-network.args.disk0 create mode 100644 tests/qemunbdkitdata/disk-cdrom-network.args.disk1 create mode 100644 tests/qemunbdkitdata/disk-cdrom-network.args.disk1.pipe.1778 create mode 100644 tests/qemunbdkitdata/disk-cdrom-network.args.disk2 create mode 100644 tests/qemunbdkitdata/disk-cdrom-network.args.disk2.pipe.1780 create mode 100644 tests/qemunbdkitdata/disk-network-http.args.disk0 create mode 100644 tests/qemunbdkitdata/disk-network-http.args.disk1 create mode 100644 tests/qemunbdkitdata/disk-network-http.args.disk2 create mode 100644 tests/qemunbdkitdata/disk-network-http.args.disk2.pipe.1778 create mode 100644 tests/qemunbdkitdata/disk-network-http.args.disk3 create mode 100644 tests/qemunbdkitdata/disk-network-http.args.disk3.pipe.1780 create mode 100644 tests/qemunbdkitdata/disk-network-source-curl-nbdkit-backing.args.disk0 create mode 100644 tests/qemunbdkitdata/disk-network-source-curl-nbdkit-backing.args.disk0.pipe.1778 create mode 100644 tests/qemunbdkitdata/disk-network-source-curl.args.1.pipe.1 create mode 100644 tests/qemunbdkitdata/disk-network-source-curl.args.disk0 create mode 100644 tests/qemunbdkitdata/disk-network-source-curl.args.disk0.pipe.1778 create mode 100644 tests/qemunbdkitdata/disk-network-source-curl.args.disk1 create mode 100644 tests/qemunbdkitdata/disk-network-source-curl.args.disk1.pipe.1780 create mode 100644 tests/qemunbdkitdata/disk-network-source-curl.args.disk1.pipe.1782 create mode 100644 tests/qemunbdkitdata/disk-network-source-curl.args.disk1.pipe.49 create mode 100644 tests/qemunbdkitdata/disk-network-source-curl.args.disk2 create mode 100644 tests/qemunbdkitdata/disk-network-source-curl.args.disk2.pipe.1782 create mode 100644 tests/qemunbdkitdata/disk-network-source-curl.args.disk2.pipe.1784 create mode 100644 tests/qemunbdkitdata/disk-network-source-curl.args.disk2.pipe.51 create mode 100644 tests/qemunbdkitdata/disk-network-source-curl.args.disk3 create mode 100644 tests/qemunbdkitdata/disk-network-source-curl.args.disk4 create mode 100644 tests/qemunbdkitdata/disk-network-ssh.args.disk0 create mode 100644 tests/qemunbdkittest.c create mode 100644 tests/qemuxml2argvdata/disk-cdrom-network-nbdkit.x86_64-latest.args create mode 120000 tests/qemuxml2argvdata/disk-cdrom-network-nbdkit.xml create mode 100644 tests/qemuxml2argvdata/disk-network-http-nbdkit.x86_64-latest.args create mode 120000 tests/qemuxml2argvdata/disk-network-http-nbdkit.xml create mode 100644 tests/qemuxml2argvdata/disk-network-source-curl-nbdkit-backing.x86_64-latest.args create mode 100644 tests/qemuxml2argvdata/disk-network-source-curl-nbdkit-backing.xml create mode 100644 tests/qemuxml2argvdata/disk-network-source-curl-nbdkit.x86_64-latest.args create mode 120000 tests/qemuxml2argvdata/disk-network-source-curl-nbdkit.xml create mode 100644 tests/qemuxml2argvdata/disk-network-source-curl.x86_64-latest.args create mode 100644 tests/qemuxml2argvdata/disk-network-source-curl.xml create mode 100644 tests/qemuxml2argvdata/disk-network-ssh-nbdkit.x86_64-latest.args create mode 120000 tests/qemuxml2argvdata/disk-network-ssh-nbdkit.xml create mode 100644 tests/qemuxml2argvdata/disk-network-ssh.x86_64-latest.args create mode 100644 tests/qemuxml2argvdata/disk-network-ssh.xml -- 2.37.3

There was support in the code for parsing protocol='ssh' on network disk sources, but it was not present in the xml schema. Add this to the schema and mention it in the documentation. Signed-off-by: Jonathon Jongsma <jjongsma@redhat.com> --- src/conf/schemas/domaincommon.rng | 1 + .../disk-network-ssh.x86_64-latest.args | 36 +++++++++++++++++++ tests/qemuxml2argvdata/disk-network-ssh.xml | 31 ++++++++++++++++ tests/qemuxml2argvtest.c | 1 + 4 files changed, 69 insertions(+) create mode 100644 tests/qemuxml2argvdata/disk-network-ssh.x86_64-latest.args create mode 100644 tests/qemuxml2argvdata/disk-network-ssh.xml diff --git a/src/conf/schemas/domaincommon.rng b/src/conf/schemas/domaincommon.rng index d346442510..e2d5ff1379 100644 --- a/src/conf/schemas/domaincommon.rng +++ b/src/conf/schemas/domaincommon.rng @@ -2144,6 +2144,7 @@ <choice> <value>sheepdog</value> <value>tftp</value> + <value>ssh</value> </choice> </attribute> <attribute name="name"/> diff --git a/tests/qemuxml2argvdata/disk-network-ssh.x86_64-latest.args b/tests/qemuxml2argvdata/disk-network-ssh.x86_64-latest.args new file mode 100644 index 0000000000..045474724b --- /dev/null +++ b/tests/qemuxml2argvdata/disk-network-ssh.x86_64-latest.args @@ -0,0 +1,36 @@ +LC_ALL=C \ +PATH=/bin \ +HOME=/tmp/lib/domain--1-QEMUGuest1 \ +USER=test \ +LOGNAME=test \ +XDG_DATA_HOME=/tmp/lib/domain--1-QEMUGuest1/.local/share \ +XDG_CACHE_HOME=/tmp/lib/domain--1-QEMUGuest1/.cache \ +XDG_CONFIG_HOME=/tmp/lib/domain--1-QEMUGuest1/.config \ +/usr/bin/qemu-system-x86_64 \ +-name guest=QEMUGuest1,debug-threads=on \ +-S \ +-object '{"qom-type":"secret","id":"masterKey0","format":"raw","file":"/tmp/lib/domain--1-QEMUGuest1/master-key.aes"}' \ +-machine pc,usb=off,dump-guest-core=off,memory-backend=pc.ram \ +-accel kvm \ +-cpu qemu64 \ +-m 214 \ +-object '{"qom-type":"memory-backend-ram","id":"pc.ram","size":224395264}' \ +-overcommit mem-lock=off \ +-smp 1,sockets=1,cores=1,threads=1 \ +-uuid c7a5fdbd-edaf-9455-926a-d65c16db1809 \ +-display none \ +-no-user-config \ +-nodefaults \ +-chardev socket,id=charmonitor,fd=1729,server=on,wait=off \ +-mon chardev=charmonitor,id=monitor,mode=control \ +-rtc base=utc \ +-no-shutdown \ +-no-acpi \ +-boot strict=on \ +-device '{"driver":"piix3-usb-uhci","id":"usb","bus":"pci.0","addr":"0x1.0x2"}' \ +-blockdev '{"driver":"ssh","path":"test.img","server":{"host":"example.org","port":"2222"},"node-name":"libvirt-1-storage","auto-read-only":true,"discard":"unmap"}' \ +-blockdev '{"node-name":"libvirt-1-format","read-only":false,"driver":"raw","file":"libvirt-1-storage"}' \ +-device '{"driver":"virtio-blk-pci","bus":"pci.0","addr":"0x2","drive":"libvirt-1-format","id":"virtio-disk0","bootindex":1}' \ +-audiodev '{"id":"audio1","driver":"none"}' \ +-sandbox on,obsolete=deny,elevateprivileges=deny,spawn=deny,resourcecontrol=deny \ +-msg timestamp=on diff --git a/tests/qemuxml2argvdata/disk-network-ssh.xml b/tests/qemuxml2argvdata/disk-network-ssh.xml new file mode 100644 index 0000000000..355add4fea --- /dev/null +++ b/tests/qemuxml2argvdata/disk-network-ssh.xml @@ -0,0 +1,31 @@ +<domain type='kvm'> + <name>QEMUGuest1</name> + <uuid>c7a5fdbd-edaf-9455-926a-d65c16db1809</uuid> + <memory unit='KiB'>219136</memory> + <currentMemory unit='KiB'>219136</currentMemory> + <vcpu placement='static'>1</vcpu> + <os> + <type arch='x86_64' machine='pc'>hvm</type> + <boot dev='hd'/> + </os> + <clock offset='utc'/> + <on_poweroff>destroy</on_poweroff> + <on_reboot>restart</on_reboot> + <on_crash>destroy</on_crash> + <devices> + <disk type='network' device='disk'> + <driver name='qemu' type='raw'/> + <source protocol='ssh' name='test.img'> + <host name='example.org' port='2222'/> + <timeout seconds='1234'/> + <readahead size='1024'/> + </source> + <target dev='vda' bus='virtio'/> + </disk> + <controller type='usb' index='0'/> + <controller type='pci' index='0' model='pci-root'/> + <input type='mouse' bus='ps2'/> + <input type='keyboard' bus='ps2'/> + <memballoon model='none'/> + </devices> +</domain> diff --git a/tests/qemuxml2argvtest.c b/tests/qemuxml2argvtest.c index 7ede68d555..ef32cae2e9 100644 --- a/tests/qemuxml2argvtest.c +++ b/tests/qemuxml2argvtest.c @@ -1325,6 +1325,7 @@ mymain(void) DO_TEST_CAPS_LATEST("disk-network-tlsx509-nbd-hostname"); DO_TEST_CAPS_VER("disk-network-tlsx509-vxhs", "5.0.0"); DO_TEST_CAPS_LATEST("disk-network-http"); + DO_TEST_CAPS_LATEST("disk-network-ssh"); driver.config->vxhsTLS = 0; VIR_FREE(driver.config->vxhsTLSx509certdir); DO_TEST_CAPS_LATEST("disk-no-boot"); -- 2.37.3

On a Thursday in 2022, Jonathon Jongsma wrote:
There was support in the code for parsing protocol='ssh' on network disk sources, but it was not present in the xml schema. Add this to the schema and mention it in the documentation.
Signed-off-by: Jonathon Jongsma <jjongsma@redhat.com> --- src/conf/schemas/domaincommon.rng | 1 + .../disk-network-ssh.x86_64-latest.args | 36 +++++++++++++++++++ tests/qemuxml2argvdata/disk-network-ssh.xml | 31 ++++++++++++++++ tests/qemuxml2argvtest.c | 1 + 4 files changed, 69 insertions(+) create mode 100644 tests/qemuxml2argvdata/disk-network-ssh.x86_64-latest.args create mode 100644 tests/qemuxml2argvdata/disk-network-ssh.xml
Reviewed-by: Ján Tomko <jtomko@redhat.com> Jano

On Tue, Nov 15, 2022 at 12:07:18 +0100, Ján Tomko wrote:
On a Thursday in 2022, Jonathon Jongsma wrote:
There was support in the code for parsing protocol='ssh' on network disk sources, but it was not present in the xml schema. Add this to the schema and mention it in the documentation.
Signed-off-by: Jonathon Jongsma <jjongsma@redhat.com> --- src/conf/schemas/domaincommon.rng | 1 + .../disk-network-ssh.x86_64-latest.args | 36 +++++++++++++++++++ tests/qemuxml2argvdata/disk-network-ssh.xml | 31 ++++++++++++++++ tests/qemuxml2argvtest.c | 1 + 4 files changed, 69 insertions(+) create mode 100644 tests/qemuxml2argvdata/disk-network-ssh.x86_64-latest.args create mode 100644 tests/qemuxml2argvdata/disk-network-ssh.xml
Reviewed-by: Ján Tomko <jtomko@redhat.com>
Note that my comments from the v2 review were not addressed.

On 12/7/22 5:15 AM, Peter Krempa wrote:
On Tue, Nov 15, 2022 at 12:07:18 +0100, Ján Tomko wrote:
On a Thursday in 2022, Jonathon Jongsma wrote:
There was support in the code for parsing protocol='ssh' on network disk sources, but it was not present in the xml schema. Add this to the schema and mention it in the documentation.
Signed-off-by: Jonathon Jongsma <jjongsma@redhat.com> --- src/conf/schemas/domaincommon.rng | 1 + .../disk-network-ssh.x86_64-latest.args | 36 +++++++++++++++++++ tests/qemuxml2argvdata/disk-network-ssh.xml | 31 ++++++++++++++++ tests/qemuxml2argvtest.c | 1 + 4 files changed, 69 insertions(+) create mode 100644 tests/qemuxml2argvdata/disk-network-ssh.x86_64-latest.args create mode 100644 tests/qemuxml2argvdata/disk-network-ssh.xml
Reviewed-by: Ján Tomko <jtomko@redhat.com>
Note that my comments from the v2 review were not addressed.
Well, I did address your comments in a way. I removed the mention of the 'ssh' protocol type from the documentation of the domain xml in order to not encourage people to try to use it. But if you want me to leave it in the documentation but add a warning, I can do that instead. Jonathon

In future commits, we will optionally use nbdkit to serve some remote disk sources. This patch queries to see whether nbdkit is installed on the host and queries it for capabilities. The data will be used in later commits. Signed-off-by: Jonathon Jongsma <jjongsma@redhat.com> --- po/POTFILES | 1 + src/qemu/meson.build | 1 + src/qemu/qemu_conf.h | 1 + src/qemu/qemu_nbdkit.c | 203 +++++++++++++++++++++++++++++++++++++++++ src/qemu/qemu_nbdkit.h | 52 +++++++++++ 5 files changed, 258 insertions(+) create mode 100644 src/qemu/qemu_nbdkit.c create mode 100644 src/qemu/qemu_nbdkit.h diff --git a/po/POTFILES b/po/POTFILES index 169e2a41dc..d96ce4415a 100644 --- a/po/POTFILES +++ b/po/POTFILES @@ -180,6 +180,7 @@ src/qemu/qemu_monitor.c src/qemu/qemu_monitor_json.c src/qemu/qemu_monitor_text.c src/qemu/qemu_namespace.c +src/qemu/qemu_nbdkit.c src/qemu/qemu_process.c src/qemu/qemu_qapi.c src/qemu/qemu_saveimage.c diff --git a/src/qemu/meson.build b/src/qemu/meson.build index 96952cc52d..101cf3591f 100644 --- a/src/qemu/meson.build +++ b/src/qemu/meson.build @@ -28,6 +28,7 @@ qemu_driver_sources = [ 'qemu_monitor_json.c', 'qemu_monitor_text.c', 'qemu_namespace.c', + 'qemu_nbdkit.c', 'qemu_process.c', 'qemu_qapi.c', 'qemu_saveimage.c', diff --git a/src/qemu/qemu_conf.h b/src/qemu/qemu_conf.h index 8cf2dd2ec5..a39510b0b1 100644 --- a/src/qemu/qemu_conf.h +++ b/src/qemu/qemu_conf.h @@ -36,6 +36,7 @@ #include "virthreadpool.h" #include "locking/lock_manager.h" #include "qemu_capabilities.h" +#include "qemu_nbdkit.h" #include "virclosecallbacks.h" #include "virhostdev.h" #include "virfile.h" diff --git a/src/qemu/qemu_nbdkit.c b/src/qemu/qemu_nbdkit.c new file mode 100644 index 0000000000..7a7248c1ef --- /dev/null +++ b/src/qemu/qemu_nbdkit.c @@ -0,0 +1,203 @@ +/* + * qemu_nbdkit.c: helpers for using nbdkit + * + * Copyright (C) 2021 Red Hat, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see + * <http://www.gnu.org/licenses/>. + * + */ + +#include <config.h> +#include <glib.h> + +#include "vircommand.h" +#include "virerror.h" +#include "virlog.h" +#include "virpidfile.h" +#include "virutil.h" +#include "qemu_block.h" +#include "qemu_conf.h" +#include "qemu_domain.h" +#include "qemu_driver.h" +#include "qemu_extdevice.h" +#include "qemu_nbdkit.h" +#include "qemu_security.h" + +#include <fcntl.h> + +#define VIR_FROM_THIS VIR_FROM_QEMU + +VIR_LOG_INIT("qemu.nbdkit"); + +VIR_ENUM_IMPL(qemuNbdkitCaps, + QEMU_NBDKIT_CAPS_LAST, + /* 0 */ + "plugin-curl", /* QEMU_NBDKIT_CAPS_PLUGIN_CURL */ + "plugin-ssh", /* QEMU_NBDKIT_CAPS_PLUGIN_SSH */ + "filter-readahead", /* QEMU_NBDKIT_CAPS_FILTER_READAHEAD */ +); + +struct _qemuNbdkitCaps { + GObject parent; + + char *path; + char *version; + + virBitmap *flags; +}; +G_DEFINE_TYPE(qemuNbdkitCaps, qemu_nbdkit_caps, G_TYPE_OBJECT); + + +static void +qemuNbdkitCheckCommandCap(qemuNbdkitCaps *nbdkit, + virCommand *cmd, + qemuNbdkitCapsFlags cap) +{ + if (virCommandRun(cmd, NULL) != 0) + return; + + VIR_DEBUG("Setting nbdkit capability %i", cap); + ignore_value(virBitmapSetBit(nbdkit->flags, cap)); +} + + +static void +qemuNbdkitQueryFilter(qemuNbdkitCaps *nbdkit, + const char *filter, + qemuNbdkitCapsFlags cap) +{ + g_autoptr(virCommand) cmd = virCommandNewArgList(nbdkit->path, + "--version", + NULL); + + virCommandAddArgPair(cmd, "--filter", filter); + + /* use null plugin to check for filter */ + virCommandAddArg(cmd, "null"); + + qemuNbdkitCheckCommandCap(nbdkit, cmd, cap); +} + + +static void +qemuNbdkitQueryPlugin(qemuNbdkitCaps *nbdkit, + const char *plugin, + qemuNbdkitCapsFlags cap) +{ + g_autoptr(virCommand) cmd = virCommandNewArgList(nbdkit->path, + plugin, + "--version", + NULL); + + qemuNbdkitCheckCommandCap(nbdkit, cmd, cap); +} + + +static void +qemuNbdkitCapsQueryPlugins(qemuNbdkitCaps *nbdkit) +{ + qemuNbdkitQueryPlugin(nbdkit, "curl", QEMU_NBDKIT_CAPS_PLUGIN_CURL); + qemuNbdkitQueryPlugin(nbdkit, "ssh", QEMU_NBDKIT_CAPS_PLUGIN_SSH); +} + + +static void +qemuNbdkitCapsQueryFilters(qemuNbdkitCaps *nbdkit) +{ + qemuNbdkitQueryFilter(nbdkit, "readahead", + QEMU_NBDKIT_CAPS_FILTER_READAHEAD); +} + + +static int +qemuNbdkitCapsQueryVersion(qemuNbdkitCaps *nbdkit) +{ + g_autoptr(virCommand) cmd = virCommandNewArgList(nbdkit->path, + "--version", + NULL); + + virCommandSetOutputBuffer(cmd, &nbdkit->version); + + if (virCommandRun(cmd, NULL) != 0) + return -1; + + VIR_DEBUG("Got nbdkit version %s", nbdkit->version); + return 0; +} + + +static void +qemuNbdkitCapsFinalize(GObject *object) +{ + qemuNbdkitCaps *nbdkit = QEMU_NBDKIT_CAPS(object); + + g_clear_pointer(&nbdkit->path, g_free); + g_clear_pointer(&nbdkit->version, g_free); + g_clear_pointer(&nbdkit->flags, virBitmapFree); + + G_OBJECT_CLASS(qemu_nbdkit_caps_parent_class)->finalize(object); +} + + +void +qemu_nbdkit_caps_init(qemuNbdkitCaps *caps) +{ + caps->flags = virBitmapNew(QEMU_NBDKIT_CAPS_LAST); + caps->version = NULL; +} + + +static void +qemu_nbdkit_caps_class_init(qemuNbdkitCapsClass *klass) +{ + GObjectClass *obj = G_OBJECT_CLASS(klass); + + obj->finalize = qemuNbdkitCapsFinalize; +} + + +qemuNbdkitCaps * +qemuNbdkitCapsNew(const char *path) +{ + qemuNbdkitCaps *caps = g_object_new(QEMU_TYPE_NBDKIT_CAPS, NULL); + caps->path = g_strdup(path); + + return caps; +} + + +G_GNUC_UNUSED static void +qemuNbdkitCapsQuery(qemuNbdkitCaps *caps) +{ + qemuNbdkitCapsQueryPlugins(caps); + qemuNbdkitCapsQueryFilters(caps); + qemuNbdkitCapsQueryVersion(caps); +} + + +bool +qemuNbdkitCapsGet(qemuNbdkitCaps *nbdkitCaps, + qemuNbdkitCapsFlags flag) +{ + return virBitmapIsBitSet(nbdkitCaps->flags, flag); +} + + +void +qemuNbdkitCapsSet(qemuNbdkitCaps *nbdkitCaps, + qemuNbdkitCapsFlags flag) +{ + ignore_value(virBitmapSetBit(nbdkitCaps->flags, flag)); +} diff --git a/src/qemu/qemu_nbdkit.h b/src/qemu/qemu_nbdkit.h new file mode 100644 index 0000000000..8ffb0f7044 --- /dev/null +++ b/src/qemu/qemu_nbdkit.h @@ -0,0 +1,52 @@ +/* + * qemu_nbdkit.h: helpers for using nbdkit + * + * Copyright (C) 2021 Red Hat, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see + * <http://www.gnu.org/licenses/>. + * + */ + +#pragma once + +#include "internal.h" +#include "virenum.h" + +typedef struct _qemuNbdkitCaps qemuNbdkitCaps; + +typedef enum { + /* 0 */ + QEMU_NBDKIT_CAPS_PLUGIN_CURL, + QEMU_NBDKIT_CAPS_PLUGIN_SSH, + QEMU_NBDKIT_CAPS_FILTER_READAHEAD, + + QEMU_NBDKIT_CAPS_LAST, +} qemuNbdkitCapsFlags; + +VIR_ENUM_DECL(qemuNbdkitCaps); + +qemuNbdkitCaps * +qemuNbdkitCapsNew(const char *path); + +bool +qemuNbdkitCapsGet(qemuNbdkitCaps *nbdkitCaps, + qemuNbdkitCapsFlags flag); + +void +qemuNbdkitCapsSet(qemuNbdkitCaps *nbdkitCaps, + qemuNbdkitCapsFlags flag); + +#define QEMU_TYPE_NBDKIT_CAPS qemu_nbdkit_caps_get_type() +G_DECLARE_FINAL_TYPE(qemuNbdkitCaps, qemu_nbdkit_caps, QEMU, NBDKIT_CAPS, GObject); -- 2.37.3

On Thu, Oct 20, 2022 at 16:58:53 -0500, Jonathon Jongsma wrote:
In future commits, we will optionally use nbdkit to serve some remote disk sources. This patch queries to see whether nbdkit is installed on the host and queries it for capabilities. The data will be used in later commits.
Signed-off-by: Jonathon Jongsma <jjongsma@redhat.com> --- po/POTFILES | 1 + src/qemu/meson.build | 1 + src/qemu/qemu_conf.h | 1 + src/qemu/qemu_nbdkit.c | 203 +++++++++++++++++++++++++++++++++++++++++ src/qemu/qemu_nbdkit.h | 52 +++++++++++ 5 files changed, 258 insertions(+) create mode 100644 src/qemu/qemu_nbdkit.c create mode 100644 src/qemu/qemu_nbdkit.h
Fails syntax-check: stdout: make: Entering directory '/home/pipo/build/libvirt/gcc/build-aux' --- /home/pipo/libvirt/po/POTFILES +++ /home/pipo/libvirt/po/POTFILES @@ -178,7 +178,6 @@ src/qemu/qemu_monitor_json.c src/qemu/qemu_monitor_text.c src/qemu/qemu_namespace.c -src/qemu/qemu_nbdkit.c src/qemu/qemu_process.c src/qemu/qemu_qapi.c src/qemu/qemu_saveimage.c make: Leaving directory '/home/pipo/build/libvirt/gcc/build-aux' stderr: you have changed the set of files with translatable diagnostics; apply the above patch make: *** [/home/pipo/libvirt/build-aux/syntax-check.mk:1248: sc_po_check] Error 1
diff --git a/po/POTFILES b/po/POTFILES index 169e2a41dc..d96ce4415a 100644 --- a/po/POTFILES +++ b/po/POTFILES @@ -180,6 +180,7 @@ src/qemu/qemu_monitor.c src/qemu/qemu_monitor_json.c src/qemu/qemu_monitor_text.c src/qemu/qemu_namespace.c +src/qemu/qemu_nbdkit.c src/qemu/qemu_process.c src/qemu/qemu_qapi.c src/qemu/qemu_saveimage.c
This hunk needs to go to a commit that actually adds translatable strings.
diff --git a/src/qemu/meson.build b/src/qemu/meson.build index 96952cc52d..101cf3591f 100644 --- a/src/qemu/meson.build +++ b/src/qemu/meson.build @@ -28,6 +28,7 @@ qemu_driver_sources = [ 'qemu_monitor_json.c', 'qemu_monitor_text.c', 'qemu_namespace.c', + 'qemu_nbdkit.c', 'qemu_process.c', 'qemu_qapi.c', 'qemu_saveimage.c', diff --git a/src/qemu/qemu_conf.h b/src/qemu/qemu_conf.h index 8cf2dd2ec5..a39510b0b1 100644 --- a/src/qemu/qemu_conf.h +++ b/src/qemu/qemu_conf.h @@ -36,6 +36,7 @@ #include "virthreadpool.h" #include "locking/lock_manager.h" #include "qemu_capabilities.h" +#include "qemu_nbdkit.h" #include "virclosecallbacks.h" #include "virhostdev.h" #include "virfile.h"
And these ideally to the commit that makes use of the functions in this file.
diff --git a/src/qemu/qemu_nbdkit.c b/src/qemu/qemu_nbdkit.c new file mode 100644 index 0000000000..7a7248c1ef --- /dev/null +++ b/src/qemu/qemu_nbdkit.c @@ -0,0 +1,203 @@ +/* + * qemu_nbdkit.c: helpers for using nbdkit + * + * Copyright (C) 2021 Red Hat, Inc.
Also consider updating the year. Once it passes the test suite: Reviewed-by: Peter Krempa <pkrempa@redhat.com>

On 12/7/22 6:34 AM, Peter Krempa wrote:
On Thu, Oct 20, 2022 at 16:58:53 -0500, Jonathon Jongsma wrote:
+ * + * Copyright (C) 2021 Red Hat, Inc.
Also consider updating the year.
For a long time I always updated the year in copyright notices when I changed a file (even still have an emacs extension that asks me if I want to do it the first time I save a file after loading it), but I recall there was some sentiment against doing that, so I stopped updating it. Did I misunderstand the discussion then, and I've been neglecting my duties? Was it that we shouldn't update the copyright if it was only trivial changes, otherwise we should?

On Fri, Dec 09, 2022 at 12:25:11 -0500, Laine Stump wrote:
On 12/7/22 6:34 AM, Peter Krempa wrote:
On Thu, Oct 20, 2022 at 16:58:53 -0500, Jonathon Jongsma wrote:
+ * + * Copyright (C) 2021 Red Hat, Inc.
Also consider updating the year.
For a long time I always updated the year in copyright notices when I changed a file (even still have an emacs extension that asks me if I want to do it the first time I save a file after loading it), but I recall there was some sentiment against doing that, so I stopped updating it. Did I misunderstand the discussion then, and I've been neglecting my duties? Was it that we shouldn't update the copyright if it was only trivial changes, otherwise we should?
I still think it's pointless to do when simpy editing an existing file. This is a new file though. In fact plenty new files don't actually even add an explicit copyright statement.

On 12/12/22 2:41 AM, Peter Krempa wrote:
On Fri, Dec 09, 2022 at 12:25:11 -0500, Laine Stump wrote:
On 12/7/22 6:34 AM, Peter Krempa wrote:
On Thu, Oct 20, 2022 at 16:58:53 -0500, Jonathon Jongsma wrote:
+ * + * Copyright (C) 2021 Red Hat, Inc.
Also consider updating the year.
For a long time I always updated the year in copyright notices when I changed a file (even still have an emacs extension that asks me if I want to do it the first time I save a file after loading it), but I recall there was some sentiment against doing that, so I stopped updating it. Did I misunderstand the discussion then, and I've been neglecting my duties? Was it that we shouldn't update the copyright if it was only trivial changes, otherwise we should?
I still think it's pointless to do when simpy editing an existing file. This is a new file though.
Ah, I didn't even notice that detail :-)
In fact plenty new files don't actually even add an explicit copyright statement.
Yeah, I just noticed a couple days ago that qemu_slirp.h has the copyright blurb, but no explicit "Copyright" or date. I wondered if maybe it was because it was just a simple .h file, and that too was something I missed the memo on...

In order to add caching of the nbdkit capabilities, we will need to compare against file modification times, etc. So look up this information when creating the nbdkit caps. Add a nbdkit_moddir build option to allow the builder to specify the location to look for nbdkit plugins and filters. Signed-off-by: Jonathon Jongsma <jjongsma@redhat.com> --- meson.build | 6 ++++++ meson_options.txt | 1 + src/qemu/qemu_nbdkit.c | 40 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 47 insertions(+) diff --git a/meson.build b/meson.build index 93de355430..e4581e74dd 100644 --- a/meson.build +++ b/meson.build @@ -1731,6 +1731,12 @@ if not get_option('driver_qemu').disabled() qemu_dbus_daemon_path = '/usr/bin/dbus-daemon' endif conf.set_quoted('QEMU_DBUS_DAEMON', qemu_dbus_daemon_path) + + nbdkit_moddir = get_option('nbdkit_moddir') + if nbdkit_moddir == '' + nbdkit_moddir = libdir / 'nbdkit' + endif + conf.set_quoted('NBDKIT_MODDIR', nbdkit_moddir) endif endif diff --git a/meson_options.txt b/meson_options.txt index 861c5577d2..d5ea4376e0 100644 --- a/meson_options.txt +++ b/meson_options.txt @@ -71,6 +71,7 @@ option('driver_vbox', type: 'feature', value: 'auto', description: 'VirtualBox X option('vbox_xpcomc_dir', type: 'string', value: '', description: 'Location of directory containing VirtualBox XPCOMC library') option('driver_vmware', type: 'feature', value: 'auto', description: 'VMware driver') option('driver_vz', type: 'feature', value: 'auto', description: 'Virtuozzo driver') +option('nbdkit_moddir', type: 'string', value: '', description: 'set the directory where nbdkit modules are located') option('secdriver_apparmor', type: 'feature', value: 'auto', description: 'use AppArmor security driver') option('apparmor_profiles', type: 'feature', value: 'auto', description: 'install apparmor profiles') diff --git a/src/qemu/qemu_nbdkit.c b/src/qemu/qemu_nbdkit.c index 7a7248c1ef..5de1021d89 100644 --- a/src/qemu/qemu_nbdkit.c +++ b/src/qemu/qemu_nbdkit.c @@ -41,6 +41,9 @@ VIR_LOG_INIT("qemu.nbdkit"); +#define NBDKIT_PLUGINDIR NBDKIT_MODDIR "/plugins" +#define NBDKIT_FILTERDIR NBDKIT_MODDIR "/filters" + VIR_ENUM_IMPL(qemuNbdkitCaps, QEMU_NBDKIT_CAPS_LAST, /* 0 */ @@ -54,6 +57,11 @@ struct _qemuNbdkitCaps { char *path; char *version; + time_t ctime; + time_t libvirtCtime; + time_t pluginDirMtime; + time_t filterDirMtime; + unsigned int libvirtVersion; virBitmap *flags; }; @@ -178,9 +186,41 @@ qemuNbdkitCapsNew(const char *path) } +static time_t +qemuNbdkitGetDirMtime(const char *moddir) +{ + struct stat st; + + if (stat(moddir, &st) < 0) { + VIR_DEBUG("Failed to stat nbdkit module directory '%s': %s", + moddir, + g_strerror(errno)); + return 0; + } + + return st.st_mtime; +} + + G_GNUC_UNUSED static void qemuNbdkitCapsQuery(qemuNbdkitCaps *caps) { + struct stat st; + + if (stat(caps->path, &st) < 0) { + VIR_DEBUG("Failed to stat nbdkit binary '%s': %s", + caps->path, + g_strerror(errno)); + caps->ctime = 0; + return; + } + + caps->ctime = st.st_ctime; + caps->filterDirMtime = qemuNbdkitGetDirMtime(NBDKIT_FILTERDIR); + caps->pluginDirMtime = qemuNbdkitGetDirMtime(NBDKIT_PLUGINDIR); + caps->libvirtCtime = virGetSelfLastChanged(); + caps->libvirtVersion = LIBVIR_VERSION_NUMBER; + qemuNbdkitCapsQueryPlugins(caps); qemuNbdkitCapsQueryFilters(caps); qemuNbdkitCapsQueryVersion(caps); -- 2.37.3

On Thu, Oct 20, 2022 at 16:58:54 -0500, Jonathon Jongsma wrote:
In order to add caching of the nbdkit capabilities, we will need to compare against file modification times, etc. So look up this information when creating the nbdkit caps.
Add a nbdkit_moddir build option to allow the builder to specify the location to look for nbdkit plugins and filters.
Signed-off-by: Jonathon Jongsma <jjongsma@redhat.com> --- meson.build | 6 ++++++ meson_options.txt | 1 + src/qemu/qemu_nbdkit.c | 40 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 47 insertions(+)
[...]
diff --git a/meson_options.txt b/meson_options.txt index 861c5577d2..d5ea4376e0 100644 --- a/meson_options.txt +++ b/meson_options.txt @@ -71,6 +71,7 @@ option('driver_vbox', type: 'feature', value: 'auto', description: 'VirtualBox X option('vbox_xpcomc_dir', type: 'string', value: '', description: 'Location of directory containing VirtualBox XPCOMC library') option('driver_vmware', type: 'feature', value: 'auto', description: 'VMware driver') option('driver_vz', type: 'feature', value: 'auto', description: 'Virtuozzo driver') +option('nbdkit_moddir', type: 'string', value: '', description: 'set the directory where nbdkit modules are located')
Too bad that we don't mention what the default is in the description message (in other places too). Reviewed-by: Peter Krempa <pkrempa@redhat.com>

Since the libvirt documentation suggests to prefer GObject over virObject, and since virObject is a GObject, change virFileCache to allow GObjects as data. Signed-off-by: Jonathon Jongsma <jjongsma@redhat.com> --- src/util/virfilecache.c | 14 ++++++++------ src/util/virfilecache.h | 2 +- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/src/util/virfilecache.c b/src/util/virfilecache.c index bad37c9f00..eaedf6db7e 100644 --- a/src/util/virfilecache.c +++ b/src/util/virfilecache.c @@ -170,7 +170,7 @@ virFileCacheLoad(virFileCache *cache, *data = g_steal_pointer(&loadData); cleanup: - virObjectUnref(loadData); + g_clear_pointer(&loadData, g_object_unref); return ret; } @@ -207,7 +207,7 @@ virFileCacheNewData(virFileCache *cache, return NULL; if (virFileCacheSave(cache, name, data) < 0) { - g_clear_pointer(&data, virObjectUnref); + g_clear_object(&data); } } @@ -239,7 +239,7 @@ virFileCacheNew(const char *dir, if (!(cache = virObjectNew(virFileCacheClass))) return NULL; - cache->table = virHashNew(virObjectUnref); + cache->table = virHashNew(g_object_unref); cache->dir = g_strdup(dir); @@ -270,7 +270,7 @@ virFileCacheValidate(virFileCache *cache, if (*data) { VIR_DEBUG("Caching data '%p' for '%s'", *data, name); if (virHashAddEntry(cache->table, name, *data) < 0) { - g_clear_pointer(data, virObjectUnref); + g_clear_pointer(data, g_object_unref); } } } @@ -300,7 +300,8 @@ virFileCacheLookup(virFileCache *cache, data = virHashLookup(cache->table, name); virFileCacheValidate(cache, name, &data); - virObjectRef(data); + if (data) + g_object_ref(data); virObjectUnlock(cache); return data; @@ -331,7 +332,8 @@ virFileCacheLookupByFunc(virFileCache *cache, data = virHashSearch(cache->table, iter, iterData, &name); virFileCacheValidate(cache, name, &data); - virObjectRef(data); + if (data) + g_object_ref(data); virObjectUnlock(cache); return data; diff --git a/src/util/virfilecache.h b/src/util/virfilecache.h index 81be8feef5..f0d220cc86 100644 --- a/src/util/virfilecache.h +++ b/src/util/virfilecache.h @@ -48,7 +48,7 @@ typedef bool * @priv: private data created together with cache * * Creates a new data based on the @name. The returned data must be - * an instance of virObject. + * an instance of GObject. * * Returns data object or NULL on error. */ -- 2.37.3

On Thu, Oct 20, 2022 at 16:58:55 -0500, Jonathon Jongsma wrote:
Since the libvirt documentation suggests to prefer GObject over virObject, and since virObject is a GObject, change virFileCache to allow GObjects as data.
Signed-off-by: Jonathon Jongsma <jjongsma@redhat.com> --- src/util/virfilecache.c | 14 ++++++++------ src/util/virfilecache.h | 2 +-
Reviewed-by: Peter Krempa <pkrempa@redhat.com>

Preparatory step for caching nbdkit capabilities. This patch implements the newData and isValid virFileCacheHandlers callback functions. Signed-off-by: Jonathon Jongsma <jjongsma@redhat.com> --- src/qemu/qemu_nbdkit.c | 93 +++++++++++++++++++++++++++++++++++++++++- src/qemu/qemu_nbdkit.h | 4 ++ 2 files changed, 96 insertions(+), 1 deletion(-) diff --git a/src/qemu/qemu_nbdkit.c b/src/qemu/qemu_nbdkit.c index 5de1021d89..9ab048e9e1 100644 --- a/src/qemu/qemu_nbdkit.c +++ b/src/qemu/qemu_nbdkit.c @@ -202,7 +202,7 @@ qemuNbdkitGetDirMtime(const char *moddir) } -G_GNUC_UNUSED static void +static void qemuNbdkitCapsQuery(qemuNbdkitCaps *caps) { struct stat st; @@ -241,3 +241,94 @@ qemuNbdkitCapsSet(qemuNbdkitCaps *nbdkitCaps, { ignore_value(virBitmapSetBit(nbdkitCaps->flags, flag)); } + + +static bool +virNbkditCapsCheckModdir(const char *moddir, + time_t expectedMtime) +{ + time_t mtime = qemuNbdkitGetDirMtime(moddir); + + if (mtime != expectedMtime) { + VIR_DEBUG("Outdated capabilities for nbdkit: module " + "directory '%s' changed (%lld vs %lld)", + moddir, + (long long)mtime, (long long)expectedMtime); + return false; + } + return true; +} + + +static bool +virNbdkitCapsIsValid(void *data, + void *privData G_GNUC_UNUSED) +{ + qemuNbdkitCaps *nbdkitCaps = data; + struct stat st; + + if (!nbdkitCaps->path) + return true; + + if (!virNbkditCapsCheckModdir(NBDKIT_PLUGINDIR, nbdkitCaps->pluginDirMtime)) + return false; + if (!virNbkditCapsCheckModdir(NBDKIT_FILTERDIR, nbdkitCaps->filterDirMtime)) + return false; + + if (nbdkitCaps->libvirtCtime != virGetSelfLastChanged() || + nbdkitCaps->libvirtVersion != LIBVIR_VERSION_NUMBER) { + VIR_DEBUG("Outdated capabilities for '%s': libvirt changed " + "(%lld vs %lld, %lu vs %lu)", + nbdkitCaps->path, + (long long)nbdkitCaps->libvirtCtime, + (long long)virGetSelfLastChanged(), + (unsigned long)nbdkitCaps->libvirtVersion, + (unsigned long)LIBVIR_VERSION_NUMBER); + return false; + } + + if (stat(nbdkitCaps->path, &st) < 0) { + VIR_DEBUG("Failed to stat nbdkit binary '%s': %s", + nbdkitCaps->path, + g_strerror(errno)); + return false; + } + + if (st.st_ctime != nbdkitCaps->ctime) { + VIR_DEBUG("Outdated capabilities for '%s': nbdkit binary changed " + "(%lld vs %lld)", + nbdkitCaps->path, + (long long)st.st_ctime, (long long)nbdkitCaps->ctime); + return false; + } + + return true; +} + + +static void* +virNbdkitCapsNewData(const char *binary, + void *privData G_GNUC_UNUSED) +{ + qemuNbdkitCaps *caps = qemuNbdkitCapsNew(binary); + qemuNbdkitCapsQuery(caps); + + return caps; +} + + +virFileCacheHandlers nbdkitCapsCacheHandlers = { + .isValid = virNbdkitCapsIsValid, + .newData = virNbdkitCapsNewData, + .loadFile = NULL, + .saveFile = NULL, + .privFree = NULL, +}; + + +virFileCache* +qemuNbdkitCapsCacheNew(const char *cachedir) +{ + g_autofree char *dir = g_build_filename(cachedir, "nbdkitcapabilities", NULL); + return virFileCacheNew(dir, "xml", &nbdkitCapsCacheHandlers); +} diff --git a/src/qemu/qemu_nbdkit.h b/src/qemu/qemu_nbdkit.h index 8ffb0f7044..f6cbedaa94 100644 --- a/src/qemu/qemu_nbdkit.h +++ b/src/qemu/qemu_nbdkit.h @@ -23,6 +23,7 @@ #include "internal.h" #include "virenum.h" +#include "virfilecache.h" typedef struct _qemuNbdkitCaps qemuNbdkitCaps; @@ -40,6 +41,9 @@ VIR_ENUM_DECL(qemuNbdkitCaps); qemuNbdkitCaps * qemuNbdkitCapsNew(const char *path); +virFileCache * +qemuNbdkitCapsCacheNew(const char *cachedir); + bool qemuNbdkitCapsGet(qemuNbdkitCaps *nbdkitCaps, qemuNbdkitCapsFlags flag); -- 2.37.3

On Thu, Oct 20, 2022 at 16:58:56 -0500, Jonathon Jongsma wrote:
Preparatory step for caching nbdkit capabilities. This patch implements the newData and isValid virFileCacheHandlers callback functions.
Signed-off-by: Jonathon Jongsma <jjongsma@redhat.com> --- src/qemu/qemu_nbdkit.c | 93 +++++++++++++++++++++++++++++++++++++++++- src/qemu/qemu_nbdkit.h | 4 ++ 2 files changed, 96 insertions(+), 1 deletion(-)
diff --git a/src/qemu/qemu_nbdkit.c b/src/qemu/qemu_nbdkit.c index 5de1021d89..9ab048e9e1 100644 --- a/src/qemu/qemu_nbdkit.c +++ b/src/qemu/qemu_nbdkit.c @@ -202,7 +202,7 @@ qemuNbdkitGetDirMtime(const char *moddir) }
-G_GNUC_UNUSED static void +static void qemuNbdkitCapsQuery(qemuNbdkitCaps *caps) { struct stat st; @@ -241,3 +241,94 @@ qemuNbdkitCapsSet(qemuNbdkitCaps *nbdkitCaps, { ignore_value(virBitmapSetBit(nbdkitCaps->flags, flag)); } + + +static bool +virNbkditCapsCheckModdir(const char *moddir, + time_t expectedMtime) +{ + time_t mtime = qemuNbdkitGetDirMtime(moddir); + + if (mtime != expectedMtime) { + VIR_DEBUG("Outdated capabilities for nbdkit: module " + "directory '%s' changed (%lld vs %lld)",
Preferrably don't use linebreaks in debug/error messages for better greppability even if it exceeds the "recomended" line lenght. (more instances in this patch). Reviewed-by: Peter Krempa <pkrempa@redhat.com>

Implement the loadFile and saveFile virFileCacheHandlers callbacks so that nbdkit capabilities are cached perstistently across daemon restarts. The format and implementation is modeled on the qemu capabilities, but simplified slightly. Signed-off-by: Jonathon Jongsma <jjongsma@redhat.com> --- src/qemu/qemu_nbdkit.c | 234 ++++++++++++++++++++++++++++++++++++++++- 1 file changed, 232 insertions(+), 2 deletions(-) diff --git a/src/qemu/qemu_nbdkit.c b/src/qemu/qemu_nbdkit.c index 9ab048e9e1..a47e169a0f 100644 --- a/src/qemu/qemu_nbdkit.c +++ b/src/qemu/qemu_nbdkit.c @@ -317,11 +317,241 @@ virNbdkitCapsNewData(const char *binary, } +static int +qemuNbdkitCapsValidateBinary(qemuNbdkitCaps *nbdkitCaps, + xmlXPathContextPtr ctxt) +{ + g_autofree char *str = NULL; + + if (!(str = virXPathString("string(./path)", ctxt))) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("missing path in nbdkit capabilities cache")); + return -1; + } + + if (STRNEQ(str, nbdkitCaps->path)) { + virReportError(VIR_ERR_INTERNAL_ERROR, + _("Expected caps for '%s' but saw '%s'"), + nbdkitCaps->path, str); + return -1; + } + + return 0; +} + + +static int +qemuNbdkitCapsParseFlags(qemuNbdkitCaps *nbdkitCaps, + xmlXPathContextPtr ctxt) +{ + g_autofree xmlNodePtr *nodes = NULL; + size_t i; + int n; + + if ((n = virXPathNodeSet("./flag", ctxt, &nodes)) < 0) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("failed to parse qemu capabilities flags")); + return -1; + } + + VIR_DEBUG("Got flags %d", n); + for (i = 0; i < n; i++) { + unsigned int flag; + + if (virXMLPropEnum(nodes[i], "name", qemuNbdkitCapsTypeFromString, + VIR_XML_PROP_REQUIRED, &flag) < 0) + return -1; + + qemuNbdkitCapsSet(nbdkitCaps, flag); + } + + return 0; +} + + +/* + * Parsing a doc that looks like + * + * <nbdkitCaps> + * <path>/some/path</path> + * <nbdkitctime>234235253</nbdkitctime> + * <plugindirmtime>234235253</plugindirmtime> + * <filterdirmtime>234235253</filterdirmtime> + * <selfctime>234235253</selfctime> + * <selfvers>1002016</selfvers> + * <flag name='foo'/> + * <flag name='bar'/> + * ... + * </nbdkitCaps> + * + * Returns 0 on success, 1 if outdated, -1 on error + */ +static int +qemuNbdkitCapsLoadCache(qemuNbdkitCaps *nbdkitCaps, + const char *filename) +{ + g_autoptr(xmlDoc) doc = NULL; + g_autoptr(xmlXPathContext) ctxt = NULL; + long long int l; + unsigned long lu; + + if (!(doc = virXMLParse(filename, NULL, NULL, "nbdkitCaps", &ctxt, NULL, false))) + return -1; + + if (virXPathLongLong("string(./selfctime)", ctxt, &l) < 0) { + virReportError(VIR_ERR_XML_ERROR, "%s", + _("missing selfctime in nbdkit capabilities XML")); + return -1; + } + nbdkitCaps->libvirtCtime = (time_t)l; + + nbdkitCaps->libvirtVersion = 0; + if (virXPathULong("string(./selfvers)", ctxt, &lu) == 0) + nbdkitCaps->libvirtVersion = lu; + + if (nbdkitCaps->libvirtCtime != virGetSelfLastChanged() || + nbdkitCaps->libvirtVersion != LIBVIR_VERSION_NUMBER) { + VIR_DEBUG("Outdated capabilities in %s: libvirt changed " + "(%lld vs %lld, %lu vs %lu), stopping load", + nbdkitCaps->path, + (long long)nbdkitCaps->libvirtCtime, + (long long)virGetSelfLastChanged(), + (unsigned long)nbdkitCaps->libvirtVersion, + (unsigned long)LIBVIR_VERSION_NUMBER); + return 1; + } + + if (qemuNbdkitCapsValidateBinary(nbdkitCaps, ctxt) < 0) + return -1; + + if (virXPathLongLong("string(./nbdkitctime)", ctxt, &l) < 0) { + virReportError(VIR_ERR_XML_ERROR, "%s", + _("missing nbdkitctime in nbdkit capabilities XML")); + return -1; + } + nbdkitCaps->ctime = (time_t)l; + + if (virXPathLongLong("string(./plugindirmtime)", ctxt, &l) < 0) { + virReportError(VIR_ERR_XML_ERROR, "%s", + _("missing plugindirmtime in nbdkit capabilities XML")); + return -1; + } + nbdkitCaps->pluginDirMtime = (time_t)l; + + if (virXPathLongLong("string(./filterdirmtime)", ctxt, &l) < 0) { + virReportError(VIR_ERR_XML_ERROR, "%s", + _("missing filterdirmtime in nbdkit capabilities XML")); + return -1; + } + nbdkitCaps->filterDirMtime = (time_t)l; + + if (qemuNbdkitCapsParseFlags(nbdkitCaps, ctxt) < 0) + return -1; + + if ((nbdkitCaps->version = virXPathString("string(./version)", ctxt)) == NULL) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("missing version in nbdkit capabilities cache")); + return -1; + } + + return 0; +} + + +static void* +virNbdkitCapsLoadFile(const char *filename, + const char *binary, + void *privData G_GNUC_UNUSED, + bool *outdated) +{ + g_autoptr(qemuNbdkitCaps) nbdkitCaps = qemuNbdkitCapsNew(binary); + int ret; + + if (!nbdkitCaps) + return NULL; + + ret = qemuNbdkitCapsLoadCache(nbdkitCaps, filename); + if (ret < 0) + return NULL; + if (ret == 1) { + *outdated = true; + return NULL; + } + + return g_steal_pointer(&nbdkitCaps); +} + + +static char* +qemuNbdkitCapsFormatCache(qemuNbdkitCaps *nbdkitCaps) +{ + g_auto(virBuffer) buf = VIR_BUFFER_INITIALIZER; + size_t i; + + virBufferAddLit(&buf, "<nbdkitCaps>\n"); + virBufferAdjustIndent(&buf, 2); + + virBufferEscapeString(&buf, "<path>%s</path>\n", + nbdkitCaps->path); + virBufferAsprintf(&buf, "<nbdkitctime>%lld</nbdkitctime>\n", + (long long)nbdkitCaps->ctime); + virBufferAsprintf(&buf, "<plugindirmtime>%lld</plugindirmtime>\n", + (long long)nbdkitCaps->pluginDirMtime); + virBufferAsprintf(&buf, "<filterdirmtime>%lld</filterdirmtime>\n", + (long long)nbdkitCaps->filterDirMtime); + virBufferAsprintf(&buf, "<selfctime>%lld</selfctime>\n", + (long long)nbdkitCaps->libvirtCtime); + virBufferAsprintf(&buf, "<selfvers>%lu</selfvers>\n", + (unsigned long)nbdkitCaps->libvirtVersion); + + for (i = 0; i < QEMU_NBDKIT_CAPS_LAST; i++) { + if (qemuNbdkitCapsGet(nbdkitCaps, i)) { + virBufferAsprintf(&buf, "<flag name='%s'/>\n", + qemuNbdkitCapsTypeToString(i)); + } + } + + virBufferAsprintf(&buf, "<version>%s</version>\n", + nbdkitCaps->version); + + virBufferAdjustIndent(&buf, -2); + virBufferAddLit(&buf, "</nbdkitCaps>\n"); + + return virBufferContentAndReset(&buf); +} + + +static int +virNbdkitCapsSaveFile(void *data, + const char *filename, + void *privData G_GNUC_UNUSED) +{ + qemuNbdkitCaps *nbdkitCaps = data; + g_autofree char *xml = NULL; + + xml = qemuNbdkitCapsFormatCache(nbdkitCaps); + + if (virFileWriteStr(filename, xml, 0600) < 0) { + virReportSystemError(errno, + _("Failed to save '%s' for '%s'"), + filename, nbdkitCaps->path); + return -1; + } + + VIR_DEBUG("Saved caps '%s' for '%s' with (%lld, %lld)", + filename, nbdkitCaps->path, + (long long)nbdkitCaps->ctime, + (long long)nbdkitCaps->libvirtCtime); + + return 0; +} + + virFileCacheHandlers nbdkitCapsCacheHandlers = { .isValid = virNbdkitCapsIsValid, .newData = virNbdkitCapsNewData, - .loadFile = NULL, - .saveFile = NULL, + .loadFile = virNbdkitCapsLoadFile, + .saveFile = virNbdkitCapsSaveFile, .privFree = NULL, }; -- 2.37.3

On Thu, Oct 20, 2022 at 16:58:57 -0500, Jonathon Jongsma wrote:
Implement the loadFile and saveFile virFileCacheHandlers callbacks so that nbdkit capabilities are cached perstistently across daemon restarts. The format and implementation is modeled on the qemu capabilities, but simplified slightly.
Signed-off-by: Jonathon Jongsma <jjongsma@redhat.com> --- src/qemu/qemu_nbdkit.c | 234 ++++++++++++++++++++++++++++++++++++++++- 1 file changed, 232 insertions(+), 2 deletions(-)
diff --git a/src/qemu/qemu_nbdkit.c b/src/qemu/qemu_nbdkit.c index 9ab048e9e1..a47e169a0f 100644 --- a/src/qemu/qemu_nbdkit.c +++ b/src/qemu/qemu_nbdkit.c
[...]
+static int +qemuNbdkitCapsLoadCache(qemuNbdkitCaps *nbdkitCaps, + const char *filename) +{ + g_autoptr(xmlDoc) doc = NULL; + g_autoptr(xmlXPathContext) ctxt = NULL; + long long int l; + unsigned long lu; + + if (!(doc = virXMLParse(filename, NULL, NULL, "nbdkitCaps", &ctxt, NULL, false))) + return -1; + + if (virXPathLongLong("string(./selfctime)", ctxt, &l) < 0) { + virReportError(VIR_ERR_XML_ERROR, "%s", + _("missing selfctime in nbdkit capabilities XML")); + return -1; + } + nbdkitCaps->libvirtCtime = (time_t)l; + + nbdkitCaps->libvirtVersion = 0; + if (virXPathULong("string(./selfvers)", ctxt, &lu) == 0) + nbdkitCaps->libvirtVersion = lu;
This no longer compiles because I've removed the 'Long' versions of the XPath helpers apply the following diff: diff --git a/src/qemu/qemu_nbdkit.c b/src/qemu/qemu_nbdkit.c index a47e169a0f..82f80d3524 100644 --- a/src/qemu/qemu_nbdkit.c +++ b/src/qemu/qemu_nbdkit.c @@ -393,7 +393,6 @@ qemuNbdkitCapsLoadCache(qemuNbdkitCaps *nbdkitCaps, g_autoptr(xmlDoc) doc = NULL; g_autoptr(xmlXPathContext) ctxt = NULL; long long int l; - unsigned long lu; if (!(doc = virXMLParse(filename, NULL, NULL, "nbdkitCaps", &ctxt, NULL, false))) return -1; @@ -406,8 +405,7 @@ qemuNbdkitCapsLoadCache(qemuNbdkitCaps *nbdkitCaps, nbdkitCaps->libvirtCtime = (time_t)l; nbdkitCaps->libvirtVersion = 0; - if (virXPathULong("string(./selfvers)", ctxt, &lu) == 0) - nbdkitCaps->libvirtVersion = lu; + virXPathUInt("string(./selfvers)", ctxt, &nbdkitCaps->libvirtVersion); if (nbdkitCaps->libvirtCtime != virGetSelfLastChanged() || nbdkitCaps->libvirtVersion != LIBVIR_VERSION_NUMBER) {
+ + if (nbdkitCaps->libvirtCtime != virGetSelfLastChanged() || + nbdkitCaps->libvirtVersion != LIBVIR_VERSION_NUMBER) { + VIR_DEBUG("Outdated capabilities in %s: libvirt changed " + "(%lld vs %lld, %lu vs %lu), stopping load", + nbdkitCaps->path,
Preferrably no linebreak in this diagnostic message.
+ (long long)nbdkitCaps->libvirtCtime, + (long long)virGetSelfLastChanged(), + (unsigned long)nbdkitCaps->libvirtVersion, + (unsigned long)LIBVIR_VERSION_NUMBER); + return 1; + } + + if (qemuNbdkitCapsValidateBinary(nbdkitCaps, ctxt) < 0) + return -1; + + if (virXPathLongLong("string(./nbdkitctime)", ctxt, &l) < 0) { + virReportError(VIR_ERR_XML_ERROR, "%s", + _("missing nbdkitctime in nbdkit capabilities XML")); + return -1; + } + nbdkitCaps->ctime = (time_t)l; + + if (virXPathLongLong("string(./plugindirmtime)", ctxt, &l) < 0) { + virReportError(VIR_ERR_XML_ERROR, "%s", + _("missing plugindirmtime in nbdkit capabilities XML")); + return -1; + } + nbdkitCaps->pluginDirMtime = (time_t)l; + + if (virXPathLongLong("string(./filterdirmtime)", ctxt, &l) < 0) { + virReportError(VIR_ERR_XML_ERROR, "%s", + _("missing filterdirmtime in nbdkit capabilities XML")); + return -1; + } + nbdkitCaps->filterDirMtime = (time_t)l; + + if (qemuNbdkitCapsParseFlags(nbdkitCaps, ctxt) < 0) + return -1; + + if ((nbdkitCaps->version = virXPathString("string(./version)", ctxt)) == NULL) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("missing version in nbdkit capabilities cache")); + return -1; + }
I presume it's an artifact from the domain caps caching code, but reporting error for any of these missing fields make much sense to me. If the cache is invalid for any reason it will be dropped and re-fetched again so the 'error' level just puts that info into the logs but no actual error will happen. I think that the above should be a debug level.
+ + return 0; +} + + +static void* +virNbdkitCapsLoadFile(const char *filename, + const char *binary, + void *privData G_GNUC_UNUSED, + bool *outdated) +{ + g_autoptr(qemuNbdkitCaps) nbdkitCaps = qemuNbdkitCapsNew(binary); + int ret; + + if (!nbdkitCaps) + return NULL; + + ret = qemuNbdkitCapsLoadCache(nbdkitCaps, filename); + if (ret < 0) + return NULL; + if (ret == 1) { + *outdated = true; + return NULL; + } + + return g_steal_pointer(&nbdkitCaps); +} + + +static char* +qemuNbdkitCapsFormatCache(qemuNbdkitCaps *nbdkitCaps) +{ + g_auto(virBuffer) buf = VIR_BUFFER_INITIALIZER; + size_t i; + + virBufferAddLit(&buf, "<nbdkitCaps>\n"); + virBufferAdjustIndent(&buf, 2); + + virBufferEscapeString(&buf, "<path>%s</path>\n", + nbdkitCaps->path); + virBufferAsprintf(&buf, "<nbdkitctime>%lld</nbdkitctime>\n", + (long long)nbdkitCaps->ctime); + virBufferAsprintf(&buf, "<plugindirmtime>%lld</plugindirmtime>\n", + (long long)nbdkitCaps->pluginDirMtime); + virBufferAsprintf(&buf, "<filterdirmtime>%lld</filterdirmtime>\n", + (long long)nbdkitCaps->filterDirMtime); + virBufferAsprintf(&buf, "<selfctime>%lld</selfctime>\n", + (long long)nbdkitCaps->libvirtCtime); + virBufferAsprintf(&buf, "<selfvers>%lu</selfvers>\n", + (unsigned long)nbdkitCaps->libvirtVersion);
Do not use 'long' type. The variable is unsigned int anyways so use the proper conversion and drop the typecast.
+ + for (i = 0; i < QEMU_NBDKIT_CAPS_LAST; i++) { + if (qemuNbdkitCapsGet(nbdkitCaps, i)) { + virBufferAsprintf(&buf, "<flag name='%s'/>\n", + qemuNbdkitCapsTypeToString(i)); + } + } + + virBufferAsprintf(&buf, "<version>%s</version>\n", + nbdkitCaps->version); + + virBufferAdjustIndent(&buf, -2); + virBufferAddLit(&buf, "</nbdkitCaps>\n"); + + return virBufferContentAndReset(&buf); +} + + +static int +virNbdkitCapsSaveFile(void *data, + const char *filename, + void *privData G_GNUC_UNUSED) +{ + qemuNbdkitCaps *nbdkitCaps = data; + g_autofree char *xml = NULL; + + xml = qemuNbdkitCapsFormatCache(nbdkitCaps); + + if (virFileWriteStr(filename, xml, 0600) < 0) { + virReportSystemError(errno, + _("Failed to save '%s' for '%s'"), + filename, nbdkitCaps->path); + return -1; + } + + VIR_DEBUG("Saved caps '%s' for '%s' with (%lld, %lld)", + filename, nbdkitCaps->path, + (long long)nbdkitCaps->ctime, + (long long)nbdkitCaps->libvirtCtime); + + return 0; +} + + virFileCacheHandlers nbdkitCapsCacheHandlers = { .isValid = virNbdkitCapsIsValid, .newData = virNbdkitCapsNewData, - .loadFile = NULL, - .saveFile = NULL, + .loadFile = virNbdkitCapsLoadFile, + .saveFile = virNbdkitCapsSaveFile, .privFree = NULL, };
-- 2.37.3

Add the virFileCache implementation for nbdkit capabilities to the qemu driver. This allows us to determine whether nbdkit is installed and which plugins are supported. it also has persistent caching and the capabilities are re-queried whenever something changes. Signed-off-by: Jonathon Jongsma <jjongsma@redhat.com> --- src/qemu/qemu_conf.h | 3 +++ src/qemu/qemu_driver.c | 4 ++++ 2 files changed, 7 insertions(+) diff --git a/src/qemu/qemu_conf.h b/src/qemu/qemu_conf.h index a39510b0b1..95c05e6888 100644 --- a/src/qemu/qemu_conf.h +++ b/src/qemu/qemu_conf.h @@ -320,6 +320,9 @@ struct _virQEMUDriver { /* Immutable pointer, self-locking APIs */ virHashAtomic *migrationErrors; + + /* Immutable pointer, self-locking APIs */ + virFileCache *nbdkitCapsCache; }; virQEMUDriverConfig *virQEMUDriverConfigNew(bool privileged, diff --git a/src/qemu/qemu_driver.c b/src/qemu/qemu_driver.c index 59a3b37b98..d42f3c2d2a 100644 --- a/src/qemu/qemu_driver.c +++ b/src/qemu/qemu_driver.c @@ -833,6 +833,9 @@ qemuStateInitialize(bool privileged, defsecmodel))) goto error; + /* find whether nbdkit is available and query its capabilities */ + qemu_driver->nbdkitCapsCache = qemuNbdkitCapsCacheNew(cfg->cacheDir); + /* If hugetlbfs is present, then we need to create a sub-directory within * it, since we can't assume the root mount point has permissions that * will let our spawned QEMU instances use it. */ @@ -1070,6 +1073,7 @@ qemuStateCleanup(void) VIR_FREE(qemu_driver->qemuImgBinary); virObjectUnref(qemu_driver->domains); virThreadPoolFree(qemu_driver->workerPool); + virObjectUnref(qemu_driver->nbdkitCapsCache); if (qemu_driver->lockFD != -1) virPidFileRelease(qemu_driver->config->stateDir, "driver", qemu_driver->lockFD); -- 2.37.3

On Thu, Oct 20, 2022 at 16:58:58 -0500, Jonathon Jongsma wrote:
Add the virFileCache implementation for nbdkit capabilities to the qemu driver. This allows us to determine whether nbdkit is installed and which plugins are supported. it also has persistent caching and the capabilities are re-queried whenever something changes.
Signed-off-by: Jonathon Jongsma <jjongsma@redhat.com> --- src/qemu/qemu_conf.h | 3 +++ src/qemu/qemu_driver.c | 4 ++++ 2 files changed, 7 insertions(+)
diff --git a/src/qemu/qemu_conf.h b/src/qemu/qemu_conf.h index a39510b0b1..95c05e6888 100644 --- a/src/qemu/qemu_conf.h +++ b/src/qemu/qemu_conf.h @@ -320,6 +320,9 @@ struct _virQEMUDriver {
/* Immutable pointer, self-locking APIs */ virHashAtomic *migrationErrors; + + /* Immutable pointer, self-locking APIs */ + virFileCache *nbdkitCapsCache; };
virQEMUDriverConfig *virQEMUDriverConfigNew(bool privileged, diff --git a/src/qemu/qemu_driver.c b/src/qemu/qemu_driver.c index 59a3b37b98..d42f3c2d2a 100644 --- a/src/qemu/qemu_driver.c +++ b/src/qemu/qemu_driver.c @@ -833,6 +833,9 @@ qemuStateInitialize(bool privileged, defsecmodel))) goto error;
+ /* find whether nbdkit is available and query its capabilities */
This comment doesn't seem accurate. IIUC virFileCacheNew simply creates the object but doesn't interrogate or load anything. Reviewed-by: Peter Krempa <pkrempa@redhat.com>

An object for storing information about a nbdkit process that is serving a specific virStorageSource. At the moment, this information is just stored in the private data of virStorageSource and not used at all. Future commits will use this data to actually start a nbdkit process. Signed-off-by: Jonathon Jongsma <jjongsma@redhat.com> --- src/qemu/qemu_conf.c | 23 ++++++++++++ src/qemu/qemu_conf.h | 3 ++ src/qemu/qemu_domain.c | 31 ++++++++++++++++ src/qemu/qemu_domain.h | 4 +++ src/qemu/qemu_nbdkit.c | 82 ++++++++++++++++++++++++++++++++++++++++++ src/qemu/qemu_nbdkit.h | 26 ++++++++++++++ 6 files changed, 169 insertions(+) diff --git a/src/qemu/qemu_conf.c b/src/qemu/qemu_conf.c index ae5bbcd138..0370429da0 100644 --- a/src/qemu/qemu_conf.c +++ b/src/qemu/qemu_conf.c @@ -1641,3 +1641,26 @@ qemuHugepageMakeBasedir(virQEMUDriver *driver, return 0; } + + +/* + * qemuGetNbdkitCaps: + * @driver: the qemu driver + * + * Gets the capabilities for Nbdkit for the specified driver. These can be used + * to determine whether a particular disk source can be served by nbdkit or + * not. + * + * Returns: a reference to qemuNbdkitCaps or NULL + */ +qemuNbdkitCaps* +qemuGetNbdkitCaps(virQEMUDriver *driver) +{ + if (!driver->nbdkitBinary) + driver->nbdkitBinary = virFindFileInPath("nbdkit"); + + if (driver->nbdkitBinary) + return virFileCacheLookup(driver->nbdkitCapsCache, driver->nbdkitBinary); + + return NULL; +} diff --git a/src/qemu/qemu_conf.h b/src/qemu/qemu_conf.h index 95c05e6888..8b4f6ce669 100644 --- a/src/qemu/qemu_conf.h +++ b/src/qemu/qemu_conf.h @@ -323,6 +323,7 @@ struct _virQEMUDriver { /* Immutable pointer, self-locking APIs */ virFileCache *nbdkitCapsCache; + char *nbdkitBinary; }; virQEMUDriverConfig *virQEMUDriverConfigNew(bool privileged, @@ -379,3 +380,5 @@ int qemuGetMemoryBackingPath(virQEMUDriver *driver, int qemuHugepageMakeBasedir(virQEMUDriver *driver, virHugeTLBFS *hugepage); + +qemuNbdkitCaps* qemuGetNbdkitCaps(virQEMUDriver *driver); diff --git a/src/qemu/qemu_domain.c b/src/qemu/qemu_domain.c index 9ef6c8bb64..3fca163415 100644 --- a/src/qemu/qemu_domain.c +++ b/src/qemu/qemu_domain.c @@ -851,6 +851,7 @@ qemuDomainStorageSourcePrivateDispose(void *obj) g_clear_pointer(&priv->encinfo, qemuDomainSecretInfoFree); g_clear_pointer(&priv->httpcookie, qemuDomainSecretInfoFree); g_clear_pointer(&priv->tlsKeySecret, qemuDomainSecretInfoFree); + g_clear_pointer(&priv->nbdkitProcess, qemuNbdkitProcessFree); } @@ -10055,6 +10056,34 @@ qemuDomainPrepareStorageSourceNFS(virStorageSource *src) } +/* qemuPrepareStorageSourceNbdkit: + * @src: source for a disk + * + * If src is an network source that is managed by nbdkit, prepare data so that + * nbdkit can be launched before the domain is started + * + * Returns true if nbdkit will be used for this source, + */ +static bool +qemuDomainPrepareStorageSourceNbdkit(virStorageSource *src, + virQEMUDriverConfig *cfg, + const char *alias, + qemuDomainObjPrivate *priv) +{ + g_autoptr(qemuNbdkitCaps) nbdkit = NULL; + + if (virStorageSourceGetActualType(src) != VIR_STORAGE_TYPE_NETWORK) + return false; + + nbdkit = qemuGetNbdkitCaps(priv->driver); + if (!nbdkit) + return false; + + return qemuNbdkitInitStorageSource(nbdkit, src, priv->libDir, + alias, cfg->user, cfg->group); +} + + /* qemuProcessPrepareStorageSourceTLS: * @source: source for a disk * @cfg: driver configuration @@ -10829,6 +10858,8 @@ qemuDomainPrepareStorageSourceBlockdevNodename(virDomainDiskDef *disk, if (qemuDomainPrepareStorageSourceNFS(src) < 0) return -1; + qemuDomainPrepareStorageSourceNbdkit(src, cfg, src->nodestorage, priv); + return 0; } diff --git a/src/qemu/qemu_domain.h b/src/qemu/qemu_domain.h index 2bbd492d62..79dd9e4329 100644 --- a/src/qemu/qemu_domain.h +++ b/src/qemu/qemu_domain.h @@ -33,6 +33,7 @@ #include "qemu_conf.h" #include "qemu_capabilities.h" #include "qemu_migration_params.h" +#include "qemu_nbdkit.h" #include "qemu_slirp.h" #include "qemu_fd.h" #include "virchrdev.h" @@ -298,6 +299,9 @@ struct _qemuDomainStorageSourcePrivate { /* key for decrypting TLS certificate */ qemuDomainSecretInfo *tlsKeySecret; + + /* an nbdkit process for serving network storage sources */ + qemuNbdkitProcess *nbdkitProcess; }; virObject *qemuDomainStorageSourcePrivateNew(void); diff --git a/src/qemu/qemu_nbdkit.c b/src/qemu/qemu_nbdkit.c index a47e169a0f..291f988c7a 100644 --- a/src/qemu/qemu_nbdkit.c +++ b/src/qemu/qemu_nbdkit.c @@ -562,3 +562,85 @@ qemuNbdkitCapsCacheNew(const char *cachedir) g_autofree char *dir = g_build_filename(cachedir, "nbdkitcapabilities", NULL); return virFileCacheNew(dir, "xml", &nbdkitCapsCacheHandlers); } + + +static qemuNbdkitProcess * +qemuNbdkitProcessNew(virStorageSource *source, + const char *pidfile, + const char *socketfile) +{ + qemuNbdkitProcess *nbdkit = g_new0(qemuNbdkitProcess, 1); + /* weak reference -- source owns this object, so it will always outlive us */ + nbdkit->source = source; + nbdkit->user = -1; + nbdkit->group = -1; + nbdkit->pid = -1; + nbdkit->pidfile = g_strdup(pidfile); + nbdkit->socketfile = g_strdup(socketfile); + + return nbdkit; +} + + +bool +qemuNbdkitInitStorageSource(qemuNbdkitCaps *caps, + virStorageSource *source, + char *statedir, + const char *alias, + uid_t user, + gid_t group) +{ + qemuDomainStorageSourcePrivate *srcPriv = qemuDomainStorageSourcePrivateFetch(source); + g_autofree char *pidname = g_strdup_printf("nbdkit-%s.pid", alias); + g_autofree char *socketname = g_strdup_printf("nbdkit-%s.socket", alias); + g_autofree char *pidfile = g_build_filename(statedir, pidname, NULL); + g_autofree char *socketfile = g_build_filename(statedir, socketname, NULL); + qemuNbdkitProcess *proc; + + if (srcPriv->nbdkitProcess) + return false; + + switch (source->protocol) { + case VIR_STORAGE_NET_PROTOCOL_HTTP: + case VIR_STORAGE_NET_PROTOCOL_HTTPS: + case VIR_STORAGE_NET_PROTOCOL_FTP: + case VIR_STORAGE_NET_PROTOCOL_FTPS: + case VIR_STORAGE_NET_PROTOCOL_TFTP: + if (!virBitmapIsBitSet(caps->flags, QEMU_NBDKIT_CAPS_PLUGIN_CURL)) + return false; + break; + case VIR_STORAGE_NET_PROTOCOL_SSH: + if (!virBitmapIsBitSet(caps->flags, QEMU_NBDKIT_CAPS_PLUGIN_SSH)) + return false; + break; + case VIR_STORAGE_NET_PROTOCOL_NONE: + case VIR_STORAGE_NET_PROTOCOL_NBD: + case VIR_STORAGE_NET_PROTOCOL_RBD: + case VIR_STORAGE_NET_PROTOCOL_SHEEPDOG: + case VIR_STORAGE_NET_PROTOCOL_GLUSTER: + case VIR_STORAGE_NET_PROTOCOL_ISCSI: + case VIR_STORAGE_NET_PROTOCOL_VXHS: + case VIR_STORAGE_NET_PROTOCOL_NFS: + case VIR_STORAGE_NET_PROTOCOL_LAST: + return false; + } + + proc = qemuNbdkitProcessNew(source, pidfile, socketfile); + proc->caps = g_object_ref(caps); + proc->user = user; + proc->group = group; + + srcPriv->nbdkitProcess = proc; + + return true; +} + + +void +qemuNbdkitProcessFree(qemuNbdkitProcess *proc) +{ + g_clear_pointer(&proc->pidfile, g_free); + g_clear_pointer(&proc->socketfile, g_free); + g_clear_object(&proc->caps); + g_free(proc); +} diff --git a/src/qemu/qemu_nbdkit.h b/src/qemu/qemu_nbdkit.h index f6cbedaa94..5cb7b0f21b 100644 --- a/src/qemu/qemu_nbdkit.h +++ b/src/qemu/qemu_nbdkit.h @@ -22,10 +22,12 @@ #pragma once #include "internal.h" +#include "storage_source_conf.h" #include "virenum.h" #include "virfilecache.h" typedef struct _qemuNbdkitCaps qemuNbdkitCaps; +typedef struct _qemuNbdkitProcess qemuNbdkitProcess; typedef enum { /* 0 */ @@ -44,6 +46,14 @@ qemuNbdkitCapsNew(const char *path); virFileCache * qemuNbdkitCapsCacheNew(const char *cachedir); +bool +qemuNbdkitInitStorageSource(qemuNbdkitCaps *nbdkitCaps, + virStorageSource *source, + char *statedir, + const char *alias, + uid_t user, + gid_t group); + bool qemuNbdkitCapsGet(qemuNbdkitCaps *nbdkitCaps, qemuNbdkitCapsFlags flag); @@ -54,3 +64,19 @@ qemuNbdkitCapsSet(qemuNbdkitCaps *nbdkitCaps, #define QEMU_TYPE_NBDKIT_CAPS qemu_nbdkit_caps_get_type() G_DECLARE_FINAL_TYPE(qemuNbdkitCaps, qemu_nbdkit_caps, QEMU, NBDKIT_CAPS, GObject); + +struct _qemuNbdkitProcess { + qemuNbdkitCaps *caps; + virStorageSource *source; + + char *pidfile; + char *socketfile; + uid_t user; + gid_t group; + pid_t pid; +}; + +void +qemuNbdkitProcessFree(qemuNbdkitProcess *proc); + +G_DEFINE_AUTOPTR_CLEANUP_FUNC(qemuNbdkitProcess, qemuNbdkitProcessFree); -- 2.37.3

On Thu, Oct 20, 2022 at 16:58:59 -0500, Jonathon Jongsma wrote:
An object for storing information about a nbdkit process that is serving a specific virStorageSource. At the moment, this information is just stored in the private data of virStorageSource and not used at all. Future commits will use this data to actually start a nbdkit process.
Signed-off-by: Jonathon Jongsma <jjongsma@redhat.com> --- src/qemu/qemu_conf.c | 23 ++++++++++++ src/qemu/qemu_conf.h | 3 ++ src/qemu/qemu_domain.c | 31 ++++++++++++++++ src/qemu/qemu_domain.h | 4 +++ src/qemu/qemu_nbdkit.c | 82 ++++++++++++++++++++++++++++++++++++++++++ src/qemu/qemu_nbdkit.h | 26 ++++++++++++++ 6 files changed, 169 insertions(+)
diff --git a/src/qemu/qemu_conf.c b/src/qemu/qemu_conf.c index ae5bbcd138..0370429da0 100644 --- a/src/qemu/qemu_conf.c +++ b/src/qemu/qemu_conf.c @@ -1641,3 +1641,26 @@ qemuHugepageMakeBasedir(virQEMUDriver *driver,
return 0; } + + +/* + * qemuGetNbdkitCaps: + * @driver: the qemu driver + * + * Gets the capabilities for Nbdkit for the specified driver. These can be used + * to determine whether a particular disk source can be served by nbdkit or + * not. + * + * Returns: a reference to qemuNbdkitCaps or NULL + */ +qemuNbdkitCaps* +qemuGetNbdkitCaps(virQEMUDriver *driver) +{ + if (!driver->nbdkitBinary) + driver->nbdkitBinary = virFindFileInPath("nbdkit"); + + if (driver->nbdkitBinary) + return virFileCacheLookup(driver->nbdkitCapsCache, driver->nbdkitBinary); + + return NULL; +} diff --git a/src/qemu/qemu_conf.h b/src/qemu/qemu_conf.h index 95c05e6888..8b4f6ce669 100644 --- a/src/qemu/qemu_conf.h +++ b/src/qemu/qemu_conf.h @@ -323,6 +323,7 @@ struct _virQEMUDriver {
/* Immutable pointer, self-locking APIs */ virFileCache *nbdkitCapsCache; + char *nbdkitBinary;
The above statement doesn't apply to 'nbdkitBinary'. It's not immutable because you update it in qemuGetNbdkitCaps and it also certainly doesn't have self-locking APIS. You can claim immutability if and only if it's assigned in qemuStateInitialize and never changed afterwards. I don't think we should cache the path though as it creates weird situations where a different version of nbdkit placed in $PATH preferentially will be picked up, but only after a restart. This brings another issue though I think that the path to modules/plugins should be detected from the actual NBDkit instance rather than hardcoded: $ nbdkit --dump-config binary=/usr/sbin/nbdkit bindir=/usr/bin exit_with_parent=yes filterdir=/usr/lib64/nbdkit/filters host_cpu=x86_64 host_os=linux-gnu libdir=/usr/lib64 mandir=/usr/share/man name=nbdkit plugindir=/usr/lib64/nbdkit/plugins root_tls_certificates_dir=/etc/pki/nbdkit sbindir=/usr/sbin selinux=yes sysconfdir=/etc tls=yes version=1.32.4 version_extra=nbdkit-1.32.4-1.fc37 version_major=1 version_minor=32 zstd=yes This way each cached version will properly see the corresponding plugin version. You also won't have a config-time option for it which can't be changed after the package is installed.

Add some helper functions to build a virCommand object and run the nbdkit process for a given virStorageSource. Signed-off-by: Jonathon Jongsma <jjongsma@redhat.com> --- src/qemu/qemu_nbdkit.c | 251 +++++++++++++++++++++++++++++++++++++++++ src/qemu/qemu_nbdkit.h | 10 ++ 2 files changed, 261 insertions(+) diff --git a/src/qemu/qemu_nbdkit.c b/src/qemu/qemu_nbdkit.c index 291f988c7a..c5b0762f8d 100644 --- a/src/qemu/qemu_nbdkit.c +++ b/src/qemu/qemu_nbdkit.c @@ -26,6 +26,7 @@ #include "virerror.h" #include "virlog.h" #include "virpidfile.h" +#include "virtime.h" #include "virutil.h" #include "qemu_block.h" #include "qemu_conf.h" @@ -636,6 +637,163 @@ qemuNbdkitInitStorageSource(qemuNbdkitCaps *caps, } +static int +qemuNbdkitProcessBuildCommandCurl(qemuNbdkitProcess *proc, + virCommand *cmd) +{ + g_autoptr(virURI) uri = qemuBlockStorageSourceGetURI(proc->source); + g_autofree char *uristring = virURIFormat(uri); + + /* nbdkit plugin name */ + virCommandAddArg(cmd, "curl"); + virCommandAddArgPair(cmd, "protocols", + virStorageNetProtocolTypeToString(proc->source->protocol)); + virCommandAddArgPair(cmd, "url", uristring); + + if (proc->source->auth) { + g_autoptr(virConnect) conn = virGetConnectSecret(); + g_autofree uint8_t *secret = NULL; + size_t secretlen = 0; + g_autofree char *password = NULL; + int secrettype; + + virCommandAddArgPair(cmd, "user", + proc->source->auth->username); + + if ((secrettype = virSecretUsageTypeFromString(proc->source->auth->secrettype)) < 0) { + virReportError(VIR_ERR_CONFIG_UNSUPPORTED, + _("invalid secret type %s"), + proc->source->auth->secrettype); + return -1; + } + + if (virSecretGetSecretString(conn, + &proc->source->auth->seclookupdef, + secrettype, + &secret, + &secretlen) < 0) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("failed to get auth secret for storage")); + return -1; + } + + /* ensure that the secret is a NULL-terminated string */ + password = g_strndup((char*)secret, secretlen); + + /* for now, just report an error rather than passing the password in + * cleartext on the commandline */ + virReportError(VIR_ERR_INTERNAL_ERROR, + "%s", + "Password not yet supported for nbdkit sources"); + return -1; + } + + if (proc->source->ncookies > 0) { + /* for now, just report an error rather than passing cookies in + * cleartext on the commandline */ + virReportError(VIR_ERR_INTERNAL_ERROR, + "%s", + "Cookies not yet supported for nbdkit sources"); + return -1; + } + + if (proc->source->sslverify == VIR_TRISTATE_BOOL_NO) { + virCommandAddArgPair(cmd, "sslverify", "false"); + } + + if (proc->source->timeout > 0) { + g_autofree char *timeout = g_strdup_printf("%llu", proc->source->timeout); + virCommandAddArgPair(cmd, "timeout", timeout); + } + + return 0; +} + + +static int +qemuNbdkitProcessBuildCommandSSH(qemuNbdkitProcess *proc, + virCommand *cmd) +{ + const char *user = NULL; + virStorageNetHostDef *host = &proc->source->hosts[0]; + g_autofree char *portstr = g_strdup_printf("%u", host->port); + + /* nbdkit plugin name */ + virCommandAddArg(cmd, "ssh"); + + virCommandAddArgPair(cmd, "host", host->name); + virCommandAddArgPair(cmd, "port", portstr); + virCommandAddArgPair(cmd, "path", proc->source->path); + + if (proc->source->auth) + user = proc->source->auth->username; + else if (proc->source->ssh_user) + user = proc->source->ssh_user; + + if (user) + virCommandAddArgPair(cmd, "user", user); + + if (proc->source->ssh_host_key_check_disabled) + virCommandAddArgPair(cmd, "verify-remote-host", "false"); + + return 0; +} + + +static virCommand * +qemuNbdkitProcessBuildCommand(qemuNbdkitProcess *proc) +{ + g_autoptr(virCommand) cmd = virCommandNewArgList(proc->caps->path, + "--exit-with-parent", + "--unix", + proc->socketfile, + "--foreground", + //"--selinux-label", + //selinux_label, + NULL); + + if (proc->source->readonly) + virCommandAddArg(cmd, "--readonly"); + + if (qemuNbdkitCapsGet(proc->caps, QEMU_NBDKIT_CAPS_FILTER_READAHEAD) && + proc->source->readahead > 0) + virCommandAddArgPair(cmd, "--filter", "readahead"); + + switch (proc->source->protocol) { + case VIR_STORAGE_NET_PROTOCOL_HTTP: + case VIR_STORAGE_NET_PROTOCOL_HTTPS: + case VIR_STORAGE_NET_PROTOCOL_FTP: + case VIR_STORAGE_NET_PROTOCOL_FTPS: + case VIR_STORAGE_NET_PROTOCOL_TFTP: + if (qemuNbdkitProcessBuildCommandCurl(proc, cmd) < 0) + return NULL; + break; + case VIR_STORAGE_NET_PROTOCOL_SSH: + if (qemuNbdkitProcessBuildCommandSSH(proc, cmd) < 0) + return NULL; + break; + + case VIR_STORAGE_NET_PROTOCOL_NONE: + case VIR_STORAGE_NET_PROTOCOL_NBD: + case VIR_STORAGE_NET_PROTOCOL_RBD: + case VIR_STORAGE_NET_PROTOCOL_SHEEPDOG: + case VIR_STORAGE_NET_PROTOCOL_GLUSTER: + case VIR_STORAGE_NET_PROTOCOL_ISCSI: + case VIR_STORAGE_NET_PROTOCOL_VXHS: + case VIR_STORAGE_NET_PROTOCOL_NFS: + case VIR_STORAGE_NET_PROTOCOL_LAST: + virReportError(VIR_ERR_NO_SUPPORT, + _("protocol '%s' is not supported by nbdkit"), + virStorageNetProtocolTypeToString(proc->source->protocol)); + return NULL; + } + + virCommandDaemonize(cmd); + + return g_steal_pointer(&cmd); +} + + void qemuNbdkitProcessFree(qemuNbdkitProcess *proc) { @@ -644,3 +802,96 @@ qemuNbdkitProcessFree(qemuNbdkitProcess *proc) g_clear_object(&proc->caps); g_free(proc); } + + +int +qemuNbdkitProcessStart(qemuNbdkitProcess *proc, + virDomainObj *vm, + virQEMUDriver *driver) +{ + g_autoptr(virCommand) cmd = NULL; + int rc; + int exitstatus = 0; + int cmdret = 0; + VIR_AUTOCLOSE errfd = -1; + virTimeBackOffVar timebackoff; + bool socketCreated = false; + + if (!(cmd = qemuNbdkitProcessBuildCommand(proc))) + return -1; + + VIR_DEBUG("starting nbdkit process for %s", proc->source->nodestorage); + virCommandSetErrorFD(cmd, &errfd); + virCommandSetPidFile(cmd, proc->pidfile); + + if (qemuExtDeviceLogCommand(driver, vm, cmd, "nbdkit") < 0) + goto error; + + if (qemuSecurityCommandRun(driver, vm, cmd, proc->user, proc->group, &exitstatus, &cmdret) < 0) + goto error; + + if (cmdret < 0 || exitstatus != 0) { + virReportError(VIR_ERR_INTERNAL_ERROR, + _("Could not start 'nbdkit'. exitstatus: %d"), exitstatus); + goto error; + } + + if ((rc = virPidFileReadPath(proc->pidfile, &proc->pid)) < 0) { + virReportSystemError(-rc, + _("Failed to read pidfile %s"), + proc->pidfile); + goto error; + } + + if (virTimeBackOffStart(&timebackoff, 1, 1000) < 0) + goto error; + + while (virTimeBackOffWait(&timebackoff)) { + if ((socketCreated = virFileExists(proc->socketfile))) + break; + + if (virProcessKill(proc->pid, 0) == 0) + continue; + + goto error; + } + + if (!socketCreated) { + virReportError(VIR_ERR_OPERATION_TIMEOUT, "%s", + _("nbdkit socket did not show up")); + goto error; + } + + return 0; + + error: + if (errfd > 0) { + g_autofree char *errbuf = g_new0(char, 1024); + if (read(errfd, errbuf, 1024) > 0) + virReportError(VIR_ERR_OPERATION_FAILED, + _("nbdkit failed to start and reported: %s"), errbuf); + } + qemuNbdkitProcessStop(proc); + return -1; +} + + +int +qemuNbdkitProcessStop(qemuNbdkitProcess *proc) +{ + int ret; + + if (proc->pid < 0) + return 0; + + VIR_DEBUG("Stopping nbdkit process %i", proc->pid); + unlink(proc->pidfile); + unlink(proc->socketfile); + + ret = virProcessKillPainfully(proc->pid, true); + + if (ret >= 0) + proc->pid = -1; + + return ret; +} diff --git a/src/qemu/qemu_nbdkit.h b/src/qemu/qemu_nbdkit.h index 5cb7b0f21b..30cab744b0 100644 --- a/src/qemu/qemu_nbdkit.h +++ b/src/qemu/qemu_nbdkit.h @@ -40,6 +40,8 @@ typedef enum { VIR_ENUM_DECL(qemuNbdkitCaps); +typedef struct _virQEMUDriver virQEMUDriver; + qemuNbdkitCaps * qemuNbdkitCapsNew(const char *path); @@ -76,6 +78,14 @@ struct _qemuNbdkitProcess { pid_t pid; }; +int +qemuNbdkitProcessStart(qemuNbdkitProcess *proc, + virDomainObj *vm, + virQEMUDriver *driver); + +int +qemuNbdkitProcessStop(qemuNbdkitProcess *proc); + void qemuNbdkitProcessFree(qemuNbdkitProcess *proc); -- 2.37.3

On Thu, Oct 20, 2022 at 16:59:00 -0500, Jonathon Jongsma wrote:
Add some helper functions to build a virCommand object and run the nbdkit process for a given virStorageSource.
Signed-off-by: Jonathon Jongsma <jjongsma@redhat.com> --- src/qemu/qemu_nbdkit.c | 251 +++++++++++++++++++++++++++++++++++++++++ src/qemu/qemu_nbdkit.h | 10 ++ 2 files changed, 261 insertions(+)
diff --git a/src/qemu/qemu_nbdkit.c b/src/qemu/qemu_nbdkit.c index 291f988c7a..c5b0762f8d 100644 --- a/src/qemu/qemu_nbdkit.c +++ b/src/qemu/qemu_nbdkit.c @@ -26,6 +26,7 @@ #include "virerror.h" #include "virlog.h" #include "virpidfile.h" +#include "virtime.h" #include "virutil.h" #include "qemu_block.h" #include "qemu_conf.h" @@ -636,6 +637,163 @@ qemuNbdkitInitStorageSource(qemuNbdkitCaps *caps, }
+static int +qemuNbdkitProcessBuildCommandCurl(qemuNbdkitProcess *proc, + virCommand *cmd) +{ + g_autoptr(virURI) uri = qemuBlockStorageSourceGetURI(proc->source); + g_autofree char *uristring = virURIFormat(uri); + + /* nbdkit plugin name */ + virCommandAddArg(cmd, "curl"); + virCommandAddArgPair(cmd, "protocols", + virStorageNetProtocolTypeToString(proc->source->protocol)); + virCommandAddArgPair(cmd, "url", uristring); + + if (proc->source->auth) { + g_autoptr(virConnect) conn = virGetConnectSecret(); + g_autofree uint8_t *secret = NULL; + size_t secretlen = 0; + g_autofree char *password = NULL; + int secrettype; + + virCommandAddArgPair(cmd, "user", + proc->source->auth->username); + + if ((secrettype = virSecretUsageTypeFromString(proc->source->auth->secrettype)) < 0) { + virReportError(VIR_ERR_CONFIG_UNSUPPORTED, + _("invalid secret type %s"), + proc->source->auth->secrettype); + return -1; + } + + if (virSecretGetSecretString(conn, + &proc->source->auth->seclookupdef, + secrettype, + &secret, + &secretlen) < 0) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("failed to get auth secret for storage")); + return -1; + } + + /* ensure that the secret is a NULL-terminated string */ + password = g_strndup((char*)secret, secretlen); + + /* for now, just report an error rather than passing the password in + * cleartext on the commandline */ + virReportError(VIR_ERR_INTERNAL_ERROR, + "%s", + "Password not yet supported for nbdkit sources");
Clever way to bypass the syntaxcheck mandating translations ... but we should probably fix the check to not allow this in the future.
+ return -1; + } + + if (proc->source->ncookies > 0) { + /* for now, just report an error rather than passing cookies in + * cleartext on the commandline */ + virReportError(VIR_ERR_INTERNAL_ERROR, + "%s", + "Cookies not yet supported for nbdkit sources"); + return -1; + } + + if (proc->source->sslverify == VIR_TRISTATE_BOOL_NO) { + virCommandAddArgPair(cmd, "sslverify", "false"); + } + + if (proc->source->timeout > 0) { + g_autofree char *timeout = g_strdup_printf("%llu", proc->source->timeout); + virCommandAddArgPair(cmd, "timeout", timeout); + } + + return 0; +} + + +static int +qemuNbdkitProcessBuildCommandSSH(qemuNbdkitProcess *proc, + virCommand *cmd) +{ + const char *user = NULL; + virStorageNetHostDef *host = &proc->source->hosts[0]; + g_autofree char *portstr = g_strdup_printf("%u", host->port); + + /* nbdkit plugin name */ + virCommandAddArg(cmd, "ssh"); + + virCommandAddArgPair(cmd, "host", host->name); + virCommandAddArgPair(cmd, "port", portstr); + virCommandAddArgPair(cmd, "path", proc->source->path); + + if (proc->source->auth) + user = proc->source->auth->username; + else if (proc->source->ssh_user) + user = proc->source->ssh_user;
So there's a bit problem with this, which also explains my reluctance to simply supporting the SSH protocol in the current state without properly designing other required parameters for SSH authentication. And even then it will most likely break "historical compatibility". So historically we didn't implement ssh protocol at all. Users (notably libguestfs) worked this around by adding a wrapper QCOW2 image and setting up the backing store string such that qemu would access the ssh image. When blockdev support was added I needed a way to port the existing functionality specifically for libguestfs. This made me add the ssh protocol and forwart the minimum set of properties to the image (thus the 'ssh_user' field. But there's another thing that was always configured out-of-band (via environment variables) and that is the ssh agent socket path or ssh key path. These are not present in the virStorageSource because they are not even configured for the blockdev 'ssh' protocol driver directly. Now with switching to nbdkit this means agent/sshkey authentication will necessarily break for such usage as we simply don't have the information. So what to do about it? We can either break the old way completely, which I guess would be mostly okay since libguestfs most likely now uses nbdkit anyways, and then design it properly. Obviously since the qemu ssh blockdev backend driver still configures stuff using environment variables thus the new configuration will be available only when nbdkit is in use. Other possibility, if we want to keep old functionality working as it was, is to avoid the use of nbdkit. Either way the implementation of SSH as done in this series simply just breaks SSH usage completely, by not being able to support the old hacky way of using agent/sshkey, not implementing any new support and not even implementing password authentication. What now? I don't really care too deeply about the hacky impl we have now. If libguestfs is no longer using I reckon we can simply break it and not deal with the fallout. I'm not sure how others care about that though. Obviously another option (besides doing a proper desing on authentication config) is to not touch anything ssh-related for now.
+ + if (user) + virCommandAddArgPair(cmd, "user", user); + + if (proc->source->ssh_host_key_check_disabled) + virCommandAddArgPair(cmd, "verify-remote-host", "false"); + + return 0; +} + + +static virCommand * +qemuNbdkitProcessBuildCommand(qemuNbdkitProcess *proc) +{ + g_autoptr(virCommand) cmd = virCommandNewArgList(proc->caps->path, + "--exit-with-parent", + "--unix", + proc->socketfile, + "--foreground", + //"--selinux-label", + //selinux_label,
Leftovers?
+ NULL); + + if (proc->source->readonly) + virCommandAddArg(cmd, "--readonly");
Note that when you want to do blockjobs qemu is currently re-opening the image in read-write mode if necessary. With the external process this won't be possible. Luckily though IIRC only ssh had write support and since we never supported SSH as the top layer image it's very unlikely that anybody ever attempted blockjobs over SSH.
+ + if (qemuNbdkitCapsGet(proc->caps, QEMU_NBDKIT_CAPS_FILTER_READAHEAD) && + proc->source->readahead > 0) + virCommandAddArgPair(cmd, "--filter", "readahead"); + + switch (proc->source->protocol) { + case VIR_STORAGE_NET_PROTOCOL_HTTP: + case VIR_STORAGE_NET_PROTOCOL_HTTPS: + case VIR_STORAGE_NET_PROTOCOL_FTP: + case VIR_STORAGE_NET_PROTOCOL_FTPS: + case VIR_STORAGE_NET_PROTOCOL_TFTP: + if (qemuNbdkitProcessBuildCommandCurl(proc, cmd) < 0) + return NULL; + break; + case VIR_STORAGE_NET_PROTOCOL_SSH: + if (qemuNbdkitProcessBuildCommandSSH(proc, cmd) < 0) + return NULL; + break; + + case VIR_STORAGE_NET_PROTOCOL_NONE: + case VIR_STORAGE_NET_PROTOCOL_NBD: + case VIR_STORAGE_NET_PROTOCOL_RBD: + case VIR_STORAGE_NET_PROTOCOL_SHEEPDOG: + case VIR_STORAGE_NET_PROTOCOL_GLUSTER: + case VIR_STORAGE_NET_PROTOCOL_ISCSI: + case VIR_STORAGE_NET_PROTOCOL_VXHS: + case VIR_STORAGE_NET_PROTOCOL_NFS: + case VIR_STORAGE_NET_PROTOCOL_LAST: + virReportError(VIR_ERR_NO_SUPPORT, + _("protocol '%s' is not supported by nbdkit"), + virStorageNetProtocolTypeToString(proc->source->protocol)); + return NULL; + } + + virCommandDaemonize(cmd); + + return g_steal_pointer(&cmd); +} + + void qemuNbdkitProcessFree(qemuNbdkitProcess *proc) { @@ -644,3 +802,96 @@ qemuNbdkitProcessFree(qemuNbdkitProcess *proc) g_clear_object(&proc->caps); g_free(proc); } + + +int +qemuNbdkitProcessStart(qemuNbdkitProcess *proc, + virDomainObj *vm, + virQEMUDriver *driver) +{ + g_autoptr(virCommand) cmd = NULL; + int rc; + int exitstatus = 0; + int cmdret = 0; + VIR_AUTOCLOSE errfd = -1; + virTimeBackOffVar timebackoff; + bool socketCreated = false; + + if (!(cmd = qemuNbdkitProcessBuildCommand(proc))) + return -1; + + VIR_DEBUG("starting nbdkit process for %s", proc->source->nodestorage); + virCommandSetErrorFD(cmd, &errfd); + virCommandSetPidFile(cmd, proc->pidfile); + + if (qemuExtDeviceLogCommand(driver, vm, cmd, "nbdkit") < 0) + goto error; + + if (qemuSecurityCommandRun(driver, vm, cmd, proc->user, proc->group, &exitstatus, &cmdret) < 0) + goto error; + + if (cmdret < 0 || exitstatus != 0) { + virReportError(VIR_ERR_INTERNAL_ERROR, + _("Could not start 'nbdkit'. exitstatus: %d"), exitstatus); + goto error; + } + + if ((rc = virPidFileReadPath(proc->pidfile, &proc->pid)) < 0) { + virReportSystemError(-rc, + _("Failed to read pidfile %s"), + proc->pidfile); + goto error; + } + + if (virTimeBackOffStart(&timebackoff, 1, 1000) < 0) + goto error; + + while (virTimeBackOffWait(&timebackoff)) { + if ((socketCreated = virFileExists(proc->socketfile)))
Can't we pass a pre-opened FD to the socket to NBDkit so that we don't have to wait for it here?
+ break; + + if (virProcessKill(proc->pid, 0) == 0) + continue; + + goto error; + } + + if (!socketCreated) { + virReportError(VIR_ERR_OPERATION_TIMEOUT, "%s", + _("nbdkit socket did not show up")); + goto error; + } + + return 0; + + error: + if (errfd > 0) { + g_autofree char *errbuf = g_new0(char, 1024); + if (read(errfd, errbuf, 1024) > 0) + virReportError(VIR_ERR_OPERATION_FAILED, + _("nbdkit failed to start and reported: %s"), errbuf); + } + qemuNbdkitProcessStop(proc); + return -1; +} + + +int +qemuNbdkitProcessStop(qemuNbdkitProcess *proc) +{ + int ret; + + if (proc->pid < 0) + return 0; + + VIR_DEBUG("Stopping nbdkit process %i", proc->pid); + unlink(proc->pidfile); + unlink(proc->socketfile); + + ret = virProcessKillPainfully(proc->pid, true);
So I presume that the intention is to never use this in read-write mode. Otherwise I'd expect graceful shutdown. You probably want an explicit check that nbdkit will never be used in read-write mode if you want to kill it this way.
+ + if (ret >= 0) + proc->pid = -1; + + return ret;

On 12/9/22 4:09 AM, Peter Krempa wrote:
On Thu, Oct 20, 2022 at 16:59:00 -0500, Jonathon Jongsma wrote:
Add some helper functions to build a virCommand object and run the nbdkit process for a given virStorageSource.
Signed-off-by: Jonathon Jongsma <jjongsma@redhat.com> --- src/qemu/qemu_nbdkit.c | 251 +++++++++++++++++++++++++++++++++++++++++ src/qemu/qemu_nbdkit.h | 10 ++ 2 files changed, 261 insertions(+)
...
+static int +qemuNbdkitProcessBuildCommandSSH(qemuNbdkitProcess *proc, + virCommand *cmd) +{ + const char *user = NULL; + virStorageNetHostDef *host = &proc->source->hosts[0]; + g_autofree char *portstr = g_strdup_printf("%u", host->port); + + /* nbdkit plugin name */ + virCommandAddArg(cmd, "ssh"); + + virCommandAddArgPair(cmd, "host", host->name); + virCommandAddArgPair(cmd, "port", portstr); + virCommandAddArgPair(cmd, "path", proc->source->path); + + if (proc->source->auth) + user = proc->source->auth->username; + else if (proc->source->ssh_user) + user = proc->source->ssh_user;
So there's a bit problem with this, which also explains my reluctance to simply supporting the SSH protocol in the current state without properly designing other required parameters for SSH authentication. And even then it will most likely break "historical compatibility".
So historically we didn't implement ssh protocol at all. Users (notably libguestfs) worked this around by adding a wrapper QCOW2 image and setting up the backing store string such that qemu would access the ssh image.
When blockdev support was added I needed a way to port the existing functionality specifically for libguestfs. This made me add the ssh protocol and forwart the minimum set of properties to the image (thus the 'ssh_user' field.
But there's another thing that was always configured out-of-band (via environment variables) and that is the ssh agent socket path or ssh key path.
These are not present in the virStorageSource because they are not even configured for the blockdev 'ssh' protocol driver directly.
Now with switching to nbdkit this means agent/sshkey authentication will necessarily break for such usage as we simply don't have the information.
So what to do about it?
We can either break the old way completely, which I guess would be mostly okay since libguestfs most likely now uses nbdkit anyways, and then design it properly. Obviously since the qemu ssh blockdev backend driver still configures stuff using environment variables thus the new configuration will be available only when nbdkit is in use.
Other possibility, if we want to keep old functionality working as it was, is to avoid the use of nbdkit.
Either way the implementation of SSH as done in this series simply just breaks SSH usage completely, by not being able to support the old hacky way of using agent/sshkey, not implementing any new support and not even implementing password authentication.
What now? I don't really care too deeply about the hacky impl we have now. If libguestfs is no longer using I reckon we can simply break it and not deal with the fallout.
I'm not sure how others care about that though.
Obviously another option (besides doing a proper desing on authentication config) is to not touch anything ssh-related for now.
Adding Rich to cc in case he has anything to add to this discussion. The problem, as I understand it, is the desire to remove the qemu block plugins from distributions. If that happens, then avoiding the use of nbdkit within libvirt won't solve anything -- the ssh use case will still break due to the absence of these qemu plugins. I'm afraid I don't have enough background information about when ssh is used and how common it is to make these decisions on my own. But I agree that the current implementation in this patch series is not complete enough to be useful. Jonathon

On Fri, Dec 09, 2022 at 11:17:22AM -0600, Jonathon Jongsma wrote:
On 12/9/22 4:09 AM, Peter Krempa wrote:
On Thu, Oct 20, 2022 at 16:59:00 -0500, Jonathon Jongsma wrote:
Add some helper functions to build a virCommand object and run the nbdkit process for a given virStorageSource.
Signed-off-by: Jonathon Jongsma <jjongsma@redhat.com> --- src/qemu/qemu_nbdkit.c | 251 +++++++++++++++++++++++++++++++++++++++++ src/qemu/qemu_nbdkit.h | 10 ++ 2 files changed, 261 insertions(+)
...
+static int +qemuNbdkitProcessBuildCommandSSH(qemuNbdkitProcess *proc, + virCommand *cmd) +{ + const char *user = NULL; + virStorageNetHostDef *host = &proc->source->hosts[0]; + g_autofree char *portstr = g_strdup_printf("%u", host->port); + + /* nbdkit plugin name */ + virCommandAddArg(cmd, "ssh"); + + virCommandAddArgPair(cmd, "host", host->name); + virCommandAddArgPair(cmd, "port", portstr); + virCommandAddArgPair(cmd, "path", proc->source->path); + + if (proc->source->auth) + user = proc->source->auth->username; + else if (proc->source->ssh_user) + user = proc->source->ssh_user;
So there's a bit problem with this, which also explains my reluctance to simply supporting the SSH protocol in the current state without properly designing other required parameters for SSH authentication. And even then it will most likely break "historical compatibility".
So historically we didn't implement ssh protocol at all. Users (notably libguestfs) worked this around by adding a wrapper QCOW2 image and setting up the backing store string such that qemu would access the ssh image.
When blockdev support was added I needed a way to port the existing functionality specifically for libguestfs. This made me add the ssh protocol and forwart the minimum set of properties to the image (thus the 'ssh_user' field.
Off topic, but I wanted to check what we do now. We generate this XML fragment: $ guestfish --format=raw -a ssh://foo/var/tmp/fedora-36.img -i -vx ... <disk device="disk" type="network"> <source protocol="ssh" name="/var/tmp/fedora-36.img"> <host name="foo"/> </source> <target dev="sda" bus="scsi"/> <driver name="qemu" type="raw" cache="writeback"/> <address type="drive" controller="0" bus="0" target="0" unit="0"/> </disk> which I think is correct(?)
But there's another thing that was always configured out-of-band (via environment variables) and that is the ssh agent socket path or ssh key path.
Right - this doesn't work in fact: Original error from libvirt: internal error: process exited while connecting to monitor: 2022-12-09T18:16:45.674960Z qemu-kvm: -blockdev {"driver":"ssh","path":"/var/tmp/fedora-36.img","server":{"host":"foo","port":"22"},"node-name":"libvirt-2-storage","cache":{"direct":false,"no-flush":false},"auto-read-only":true,"discard":"unmap"}: failed to authenticate using publickey authentication and the identities held by your ssh-agent [code=1 int1=-1] Am I doing something wrong here? I have to confess it's been a very long time since I tried to use the ssh support directly in libguestfs (see below for why). So I don't quite remember how it's supposed to work, or else it broke and I didn't notice.
These are not present in the virStorageSource because they are not even configured for the blockdev 'ssh' protocol driver directly.
Now with switching to nbdkit this means agent/sshkey authentication will necessarily break for such usage as we simply don't have the information.
So what to do about it?
We can either break the old way completely, which I guess would be mostly okay since libguestfs most likely now uses nbdkit anyways, and then design it properly. Obviously since the qemu ssh blockdev backend driver still configures stuff using environment variables thus the new configuration will be available only when nbdkit is in use.
So for libguestfs as above we're using libvirt (and hence indirectly qemu's ssh). That's broken, but no one reported the bug so I guess it's little used. But you're right that for virt-v2v we switched to using nbdkit-ssh-plugin a while back and that really is used and tested extensively. (In this case we use libvirt + qemu's nbd driver to talk to the nbdkit endpoint.)
Other possibility, if we want to keep old functionality working as it was, is to avoid the use of nbdkit.
Either way the implementation of SSH as done in this series simply just breaks SSH usage completely, by not being able to support the old hacky way of using agent/sshkey, not implementing any new support and not even implementing password authentication.
What now? I don't really care too deeply about the hacky impl we have now. If libguestfs is no longer using I reckon we can simply break it and not deal with the fallout.
I'm not sure how others care about that though.
Obviously another option (besides doing a proper desing on authentication config) is to not touch anything ssh-related for now.
Adding Rich to cc in case he has anything to add to this discussion.
The problem, as I understand it, is the desire to remove the qemu block plugins from distributions. If that happens, then avoiding the use of nbdkit within libvirt won't solve anything -- the ssh use case will still break due to the absence of these qemu plugins.
I'm afraid I don't have enough background information about when ssh is used and how common it is to make these decisions on my own. But I agree that the current implementation in this patch series is not complete enough to be useful.
From the libguestfs point of view, we are generating the <disk .. type="network"><source protocol="ssh"> fragment above and hoping that libvirt's implementation does the right thing.
Rich. -- Richard Jones, Virtualization Group, Red Hat http://people.redhat.com/~rjones Read my programming and virtualization blog: http://rwmj.wordpress.com virt-builder quickly builds VMs from scratch http://libguestfs.org/virt-builder.1.html

On Fri, Dec 09, 2022 at 06:23:59PM +0000, Richard W.M. Jones wrote:
$ guestfish --format=raw -a ssh://foo/var/tmp/fedora-36.img -i -vx
...
<disk device="disk" type="network"> <source protocol="ssh" name="/var/tmp/fedora-36.img"> <host name="foo"/> </source> <target dev="sda" bus="scsi"/> <driver name="qemu" type="raw" cache="writeback"/> <address type="drive" controller="0" bus="0" target="0" unit="0"/> </disk>
Actually I remembered in the "read-only"[1] case we do create a qcow2 overlay: $ guestfish --ro --format=raw -a ssh://foo/var/tmp/fedora-36.img -i ... libguestfs: trace: disk_create "/tmp/libguestfszdXqqC/overlay1.qcow2" "qcow2" -1 "backingfile:ssh://foo/var/tmp/fedora-36.img" "backingformat:raw" libguestfs: command: run: qemu-img libguestfs: command: run: \ create libguestfs: command: run: \ -f qcow2 libguestfs: command: run: \ -o backing_file=ssh://foo/var/tmp/fedora-36.img,backing_fmt=raw libguestfs: command: run: \ /tmp/libguestfszdXqqC/overlay1.qcow2 Formatting '/tmp/libguestfszdXqqC/overlay1.qcow2', fmt=qcow2 cluster_size=65536 extended_l2=off compression_type=zlib size=6442450944 backing_file=ssh://foo/var/tmp/fedora-36.img backing_fmt=raw lazy_refcounts=off refcount_bits=16 ... <disk device="disk" type="file"> <source file="/tmp/libguestfszdXqqC/overlay1.qcow2"/> <target dev="sda" bus="scsi"/> <driver name="qemu" type="qcow2" cache="unsafe"/> <address type="drive" controller="0" bus="0" target="0" unit="0"/> </disk> which might be what you were thinking about. Actually this doesn't work either: Original error from libvirt: internal error: process exited while connecting to monitor: 2022-12-09T20:17:41.074400Z qemu-kvm: -blockdev {"driver":"ssh","path":"var/tmp/fedora-36.img","server":{"host":"foo","port":"22"},"node-name":"libvirt-4-storage","cache":{"direct":false,"no-flush":true},"auto-read-only":true,"discard":"unmap"}: failed to authenticate using publickey authentication and the identities held by your ssh-agent [code=1 int1=-1] Rich. [1] Not really "read-only" - we must create a r/w overlay because otherwise replaying journals in filesystems does not work. The overlay is discarded afterwards. -- Richard Jones, Virtualization Group, Red Hat http://people.redhat.com/~rjones Read my programming and virtualization blog: http://rwmj.wordpress.com libguestfs lets you edit virtual machines. Supports shell scripting, bindings from many languages. http://libguestfs.org

On Fri, Dec 09, 2022 at 18:23:59 +0000, Richard W.M. Jones wrote:
On Fri, Dec 09, 2022 at 11:17:22AM -0600, Jonathon Jongsma wrote:
On 12/9/22 4:09 AM, Peter Krempa wrote:
On Thu, Oct 20, 2022 at 16:59:00 -0500, Jonathon Jongsma wrote:
Add some helper functions to build a virCommand object and run the nbdkit process for a given virStorageSource.
Signed-off-by: Jonathon Jongsma <jjongsma@redhat.com> --- src/qemu/qemu_nbdkit.c | 251 +++++++++++++++++++++++++++++++++++++++++ src/qemu/qemu_nbdkit.h | 10 ++ 2 files changed, 261 insertions(+)
...
+static int +qemuNbdkitProcessBuildCommandSSH(qemuNbdkitProcess *proc, + virCommand *cmd) +{ + const char *user = NULL; + virStorageNetHostDef *host = &proc->source->hosts[0]; + g_autofree char *portstr = g_strdup_printf("%u", host->port); + + /* nbdkit plugin name */ + virCommandAddArg(cmd, "ssh"); + + virCommandAddArgPair(cmd, "host", host->name); + virCommandAddArgPair(cmd, "port", portstr); + virCommandAddArgPair(cmd, "path", proc->source->path); + + if (proc->source->auth) + user = proc->source->auth->username; + else if (proc->source->ssh_user) + user = proc->source->ssh_user;
So there's a bit problem with this, which also explains my reluctance to simply supporting the SSH protocol in the current state without properly designing other required parameters for SSH authentication. And even then it will most likely break "historical compatibility".
So historically we didn't implement ssh protocol at all. Users (notably libguestfs) worked this around by adding a wrapper QCOW2 image and setting up the backing store string such that qemu would access the ssh image.
When blockdev support was added I needed a way to port the existing functionality specifically for libguestfs. This made me add the ssh protocol and forwart the minimum set of properties to the image (thus the 'ssh_user' field.
Off topic, but I wanted to check what we do now. We generate this XML fragment:
$ guestfish --format=raw -a ssh://foo/var/tmp/fedora-36.img -i -vx
...
<disk device="disk" type="network"> <source protocol="ssh" name="/var/tmp/fedora-36.img"> <host name="foo"/> </source> <target dev="sda" bus="scsi"/> <driver name="qemu" type="raw" cache="writeback"/> <address type="drive" controller="0" bus="0" target="0" unit="0"/> </disk>
which I think is correct(?)
Kind of ... it uses the un-documented way to instantiate SSH which is undocumented due to the reasons I mentioned originally, but it should work to the extent it used to in the past with qemu.
But there's another thing that was always configured out-of-band (via environment variables) and that is the ssh agent socket path or ssh key path.
Right - this doesn't work in fact:
Original error from libvirt: internal error: process exited while connecting to monitor: 2022-12-09T18:16:45.674960Z qemu-kvm: -blockdev {"driver":"ssh","path":"/var/tmp/fedora-36.img","server":{"host":"foo","port":"22"},"node-name":"libvirt-2-storage","cache":{"direct":false,"no-flush":false},"auto-read-only":true,"discard":"unmap"}: failed to authenticate using publickey authentication and the identities held by your ssh-agent [code=1 int1=-1]
The error message you are getting here means that both agent and privkey authentication failed.
Am I doing something wrong here? I have to confess it's been a very long time since I tried to use the ssh support directly in libguestfs (see below for why). So I don't quite remember how it's supposed to work, or else it broke and I didn't notice.
In the configuration that guestfish/libguestfs uses it simply uses a SSH disk and doesn't configure the 'SSH_AUTH_SOCK=' environment variable via the 'qemu' namespace env pass options. libvirt will not pass it by design so agent based authentication is out of the question. Since the XML doesn't have any way to specify which auth to use the last fallback option is to use the default ssh key/identity. Qemu also doesn't have any option to unlock the ssh key so really the only option is password-less keys. If you have them configured it still works at least in my testing. This obviously works only for 'session' mode connections, as in the system/privileged mode qemu is spawned as a different user which doesn't even have a home directory so there's no way to give it ssh keys. This means that ssh-backed disks worked only: 1) in session mode, if you env-passed the agent, or used password-less keys 2) in system mode, if you env-passed the agent (and hacked around selinux somehow) Based on the above, the only working option for now is session-mode with password-less keys.
These are not present in the virStorageSource because they are not even configured for the blockdev 'ssh' protocol driver directly.
Now with switching to nbdkit this means agent/sshkey authentication will necessarily break for such usage as we simply don't have the information.
So what to do about it?
We can either break the old way completely, which I guess would be mostly okay since libguestfs most likely now uses nbdkit anyways, and then design it properly. Obviously since the qemu ssh blockdev backend driver still configures stuff using environment variables thus the new configuration will be available only when nbdkit is in use.
So for libguestfs as above we're using libvirt (and hence indirectly qemu's ssh). That's broken, but no one reported the bug so I guess it's little used.
Or the users are using it with password-less keys as their only option.
But you're right that for virt-v2v we switched to using nbdkit-ssh-plugin a while back and that really is used and tested extensively. (In this case we use libvirt + qemu's nbd driver to talk to the nbdkit endpoint.)
Other possibility, if we want to keep old functionality working as it was, is to avoid the use of nbdkit.
Either way the implementation of SSH as done in this series simply just breaks SSH usage completely, by not being able to support the old hacky way of using agent/sshkey, not implementing any new support and not even implementing password authentication.
What now? I don't really care too deeply about the hacky impl we have now. If libguestfs is no longer using I reckon we can simply break it and not deal with the fallout.
I'm not sure how others care about that though.
Obviously another option (besides doing a proper desing on authentication config) is to not touch anything ssh-related for now.
Adding Rich to cc in case he has anything to add to this discussion.
The problem, as I understand it, is the desire to remove the qemu block plugins from distributions. If that happens, then avoiding the use of nbdkit within libvirt won't solve anything -- the ssh use case will still break due to the absence of these qemu plugins.
I'm afraid I don't have enough background information about when ssh is used and how common it is to make these decisions on my own. But I agree that the current implementation in this patch series is not complete enough to be useful.
From the libguestfs point of view, we are generating the <disk .. type="network"><source protocol="ssh"> fragment above and hoping that libvirt's implementation does the right thing.
Well it does in a very specific limited set of options which were implemented as a hack to prevent breakage when we switched to blockdev mode. Now with these patches 'nbdkit' would most likely work the same way in the exact scenario where session mode libvirt is used with password-less keys. What will break is if anybody used agent and passed it via the environment because the nbdkit process will not get the environment override which was specified for qemu. (And it should not get it either). Obviously env pass is where we don't give strong non-breakage guarantees. Ideally we'll provide a better interface for setting authentication with ssh which will allow you to pass specific keys, agent socket, or password for password-based auth, but since libguestfs is not actually passing the agent socket (any more?) and simply works only with the default identity, which should work also with nbdkit. In my opinion, since there's no known user of agent-based auth passed via qemu:env, we can break it as it was never really supported.

On Thu, Oct 20, 2022 at 16:59:00 -0500, Jonathon Jongsma wrote:
Add some helper functions to build a virCommand object and run the nbdkit process for a given virStorageSource.
Signed-off-by: Jonathon Jongsma <jjongsma@redhat.com> --- src/qemu/qemu_nbdkit.c | 251 +++++++++++++++++++++++++++++++++++++++++ src/qemu/qemu_nbdkit.h | 10 ++ 2 files changed, 261 insertions(+)
[...]
+static virCommand * +qemuNbdkitProcessBuildCommand(qemuNbdkitProcess *proc) +{ + g_autoptr(virCommand) cmd = virCommandNewArgList(proc->caps->path, + "--exit-with-parent",
The documentation for this option states: If the parent process exits, we exit. This can be used to avoid complicated cleanup or orphaned nbdkit processes. There are some important caveats with this, see "EXIT WITH PARENT" in nbdkit-captive(1). But this doesn't really make much sense in our case. We very specifically daemonize the process so it becomes parent of init, thus there's no point waiting for the parent.
+ "--unix", + proc->socketfile, + "--foreground", + //"--selinux-label", + //selinux_label, + NULL);
[...]
+qemuNbdkitProcessStart(qemuNbdkitProcess *proc, + virDomainObj *vm, + virQEMUDriver *driver) +{ + g_autoptr(virCommand) cmd = NULL; + int rc; + int exitstatus = 0; + int cmdret = 0; + VIR_AUTOCLOSE errfd = -1; + virTimeBackOffVar timebackoff; + bool socketCreated = false; + + if (!(cmd = qemuNbdkitProcessBuildCommand(proc))) + return -1; + + VIR_DEBUG("starting nbdkit process for %s", proc->source->nodestorage); + virCommandSetErrorFD(cmd, &errfd); + virCommandSetPidFile(cmd, proc->pidfile); + + if (qemuExtDeviceLogCommand(driver, vm, cmd, "nbdkit") < 0) + goto error; + + if (qemuSecurityCommandRun(driver, vm, cmd, proc->user, proc->group, &exitstatus, &cmdret) < 0) + goto error; + + if (cmdret < 0 || exitstatus != 0) { + virReportError(VIR_ERR_INTERNAL_ERROR, + _("Could not start 'nbdkit'. exitstatus: %d"), exitstatus); + goto error; + } + + if ((rc = virPidFileReadPath(proc->pidfile, &proc->pid)) < 0) { + virReportSystemError(-rc, + _("Failed to read pidfile %s"), + proc->pidfile); + goto error; + } + + if (virTimeBackOffStart(&timebackoff, 1, 1000) < 0) + goto error; + + while (virTimeBackOffWait(&timebackoff)) { + if ((socketCreated = virFileExists(proc->socketfile))) + break; + + if (virProcessKill(proc->pid, 0) == 0) + continue; + + goto error; + } + + if (!socketCreated) { + virReportError(VIR_ERR_OPERATION_TIMEOUT, "%s", + _("nbdkit socket did not show up")); + goto error; + } + + return 0; + + error: + if (errfd > 0) { + g_autofree char *errbuf = g_new0(char, 1024); + if (read(errfd, errbuf, 1024) > 0) + virReportError(VIR_ERR_OPERATION_FAILED, + _("nbdkit failed to start and reported: %s"), errbuf);
This error reporting doesn't seem to work. I tried starting a VM and got an error from qemu that the NBD socket is not available: error: internal error: process exited while connecting to monitor: 2022-12-09T15:12:25.558050Z qemu-system-x86_64: -blockdev {"driver":"nbd","server":{"type":"unix","path":"/var/lib/libvirt/qemu/domain-11-cd/nbdkit-libvirt-1-storage.socket"},"node-name":"libvirt-1-storage","auto-read-only":true,"discard":"unmap"}: Requested export not available So I enabled logging from nbdkit into logging into the system journal and got: Dec 09 16:07:55 speedmetal nbdkit[2115190]: curl[1]: problem doing HEAD request to fetch size of URL [http://HOST:80/name]: Unsupported protocol: Protocol "https" not supported or disabled in libcurl Which is true, the tested host forces https, but libvirt didn't report it at all. I think you'll need to convert this to properly capture the stdout/err via virtlogd as we do with other external process helpers.

On 12/9/22 9:21 AM, Peter Krempa wrote:
On Thu, Oct 20, 2022 at 16:59:00 -0500, Jonathon Jongsma wrote:
Add some helper functions to build a virCommand object and run the nbdkit process for a given virStorageSource.
Signed-off-by: Jonathon Jongsma <jjongsma@redhat.com> --- src/qemu/qemu_nbdkit.c | 251 +++++++++++++++++++++++++++++++++++++++++ src/qemu/qemu_nbdkit.h | 10 ++ 2 files changed, 261 insertions(+)
[...]
+static virCommand * +qemuNbdkitProcessBuildCommand(qemuNbdkitProcess *proc) +{ + g_autoptr(virCommand) cmd = virCommandNewArgList(proc->caps->path, + "--exit-with-parent",
The documentation for this option states:
If the parent process exits, we exit. This can be used to avoid complicated cleanup or orphaned nbdkit processes. There are some important caveats with this, see "EXIT WITH PARENT" in nbdkit-captive(1).
But this doesn't really make much sense in our case. We very specifically daemonize the process so it becomes parent of init, thus there's no point waiting for the parent.
+ "--unix", + proc->socketfile, + "--foreground", + //"--selinux-label", + //selinux_label, + NULL);
[...]
+qemuNbdkitProcessStart(qemuNbdkitProcess *proc, + virDomainObj *vm, + virQEMUDriver *driver) +{ + g_autoptr(virCommand) cmd = NULL; + int rc; + int exitstatus = 0; + int cmdret = 0; + VIR_AUTOCLOSE errfd = -1; + virTimeBackOffVar timebackoff; + bool socketCreated = false; + + if (!(cmd = qemuNbdkitProcessBuildCommand(proc))) + return -1; + + VIR_DEBUG("starting nbdkit process for %s", proc->source->nodestorage); + virCommandSetErrorFD(cmd, &errfd); + virCommandSetPidFile(cmd, proc->pidfile); + + if (qemuExtDeviceLogCommand(driver, vm, cmd, "nbdkit") < 0) + goto error; + + if (qemuSecurityCommandRun(driver, vm, cmd, proc->user, proc->group, &exitstatus, &cmdret) < 0) + goto error; + + if (cmdret < 0 || exitstatus != 0) { + virReportError(VIR_ERR_INTERNAL_ERROR, + _("Could not start 'nbdkit'. exitstatus: %d"), exitstatus); + goto error; + } + + if ((rc = virPidFileReadPath(proc->pidfile, &proc->pid)) < 0) { + virReportSystemError(-rc, + _("Failed to read pidfile %s"), + proc->pidfile); + goto error; + } + + if (virTimeBackOffStart(&timebackoff, 1, 1000) < 0) + goto error; + + while (virTimeBackOffWait(&timebackoff)) { + if ((socketCreated = virFileExists(proc->socketfile))) + break; + + if (virProcessKill(proc->pid, 0) == 0) + continue; + + goto error; + } + + if (!socketCreated) { + virReportError(VIR_ERR_OPERATION_TIMEOUT, "%s", + _("nbdkit socket did not show up")); + goto error; + } + + return 0; + + error: + if (errfd > 0) { + g_autofree char *errbuf = g_new0(char, 1024); + if (read(errfd, errbuf, 1024) > 0) + virReportError(VIR_ERR_OPERATION_FAILED, + _("nbdkit failed to start and reported: %s"), errbuf);
This error reporting doesn't seem to work. I tried starting a VM and got an error from qemu that the NBD socket is not available:
error: internal error: process exited while connecting to monitor: 2022-12-09T15:12:25.558050Z qemu-system-x86_64: -blockdev {"driver":"nbd","server":{"type":"unix","path":"/var/lib/libvirt/qemu/domain-11-cd/nbdkit-libvirt-1-storage.socket"},"node-name":"libvirt-1-storage","auto-read-only":true,"discard":"unmap"}: Requested export not available
So I enabled logging from nbdkit into logging into the system journal and got:
Dec 09 16:07:55 speedmetal nbdkit[2115190]: curl[1]: problem doing HEAD request to fetch size of URL [http://HOST:80/name]: Unsupported protocol: Protocol "https" not supported or disabled in libcurl
Which is true, the tested host forces https, but libvirt didn't report it at all.
I think you'll need to convert this to properly capture the stdout/err via virtlogd as we do with other external process helpers.
Adding Rich to cc on this subthread as well. Peter and I were discussing this on IRC and were wondering if nbdkit provides (or could provide) something that could help with error reporting here. The issue is that if you configure the the network source incorrectly, nbdkit will start up fine, and won't report an error until qemu connects to the unix socket and tries to access the nbd export. At that point, we report the unhelpful error Peter mentions above ("Requested export not available") rather than something helpful (e.g. "Couldn't resolve host name: site-that-doesnt-exist.com"). One possibility that we discussed was that libvirt could link against libnbd and try to open the socket ourselves before attempting to pass it to qemu. This would theoretically trigger an error from nbdkit and allow us to report that error before we even get to the point of starting qemu. That would at least help catch misconfigurations. But it seems unfortunate to add a new dependency just for that. If we could simply tell nbdkit to try to access the remote host and fail early rather than waiting for a nbd client, that would be nicer from my point of view. Any thoughts? Based on my past experience, I wouldn't be surprised if you've already though of this and there's a plugin that already provides this functionality ;) Jonathon

Add new DO_TEST_CAPS_LATEST_NBDKIT macro to test xml2argv for various nbdkit capability scenarios. Signed-off-by: Jonathon Jongsma <jjongsma@redhat.com> --- src/qemu/qemu_nbdkit.c | 20 +++++++++++++++++--- tests/qemuxml2argvtest.c | 11 +++++++++++ tests/testutilsqemu.c | 27 +++++++++++++++++++++++++++ tests/testutilsqemu.h | 5 +++++ 4 files changed, 60 insertions(+), 3 deletions(-) diff --git a/src/qemu/qemu_nbdkit.c b/src/qemu/qemu_nbdkit.c index c5b0762f8d..59c452a15e 100644 --- a/src/qemu/qemu_nbdkit.c +++ b/src/qemu/qemu_nbdkit.c @@ -263,10 +263,16 @@ virNbkditCapsCheckModdir(const char *moddir, static bool virNbdkitCapsIsValid(void *data, - void *privData G_GNUC_UNUSED) + void *privData) { qemuNbdkitCaps *nbdkitCaps = data; struct stat st; + /* when run under test, we will use privData as a signal to indicate that + * we shouldn't touch the filesystem */ + bool skipValidation = (privData != NULL); + + if (skipValidation) + return true; if (!nbdkitCaps->path) return true; @@ -309,9 +315,17 @@ virNbdkitCapsIsValid(void *data, static void* virNbdkitCapsNewData(const char *binary, - void *privData G_GNUC_UNUSED) + void *privData) { - qemuNbdkitCaps *caps = qemuNbdkitCapsNew(binary); + /* when run under test, we will use privData as a signal to indicate that + * we shouldn't touch the filesystem */ + bool skipNewData = (privData != NULL); + qemuNbdkitCaps *caps = NULL; + + if (skipNewData) + return NULL; + + caps = qemuNbdkitCapsNew(binary); qemuNbdkitCapsQuery(caps); return caps; diff --git a/tests/qemuxml2argvtest.c b/tests/qemuxml2argvtest.c index ef32cae2e9..0032bafdce 100644 --- a/tests/qemuxml2argvtest.c +++ b/tests/qemuxml2argvtest.c @@ -670,6 +670,14 @@ testCompareXMLToArgv(const void *data) if (rc < 0) goto cleanup; + if (info->nbdkitCaps) { + if (virFileCacheInsertData(driver.nbdkitCapsCache, TEST_NBDKIT_PATH, + g_object_ref(info->nbdkitCaps)) < 0) { + g_object_unref(info->nbdkitCaps); + goto cleanup; + } + } + if (info->migrateFrom && !(migrateURI = qemuMigrationDstGetURI(info->migrateFrom, info->migrateFd))) @@ -927,6 +935,9 @@ mymain(void) # define DO_TEST_CAPS_ARCH_VER(name, arch, ver) \ DO_TEST_CAPS_ARCH_VER_FULL(name, arch, ver, ARG_END) +# define DO_TEST_CAPS_LATEST_NBDKIT(name, ...) \ + DO_TEST_CAPS_ARCH_LATEST_FULL(name, "x86_64", ARG_NBDKIT_CAPS, __VA_ARGS__, QEMU_NBDKIT_CAPS_LAST, ARG_END) + # define DO_TEST_CAPS_LATEST(name) \ DO_TEST_CAPS_ARCH_LATEST(name, "x86_64") diff --git a/tests/testutilsqemu.c b/tests/testutilsqemu.c index 6d3decdc16..386042aa79 100644 --- a/tests/testutilsqemu.c +++ b/tests/testutilsqemu.c @@ -131,6 +131,10 @@ virFindFileInPath(const char *file) return g_strdup_printf("/usr/bin/%s", file); } + if (g_str_equal(file, "nbdkit")) { + return g_strdup(TEST_NBDKIT_PATH); + } + /* Nothing in tests should be relying on real files * in host OS, so we return NULL to try to force * an error in such a case @@ -422,6 +426,7 @@ void qemuTestDriverFree(virQEMUDriver *driver) virObjectUnref(driver->caps); virObjectUnref(driver->config); virObjectUnref(driver->securityManager); + g_clear_object(&driver->nbdkitCapsCache); virCPUDefFree(cpuDefault); virCPUDefFree(cpuHaswell); @@ -665,6 +670,12 @@ int qemuTestDriverInit(virQEMUDriver *driver) if (!driver->qemuCapsCache) goto error; + driver->nbdkitCapsCache = qemuNbdkitCapsCacheNew("/dev/null"); + /* the nbdkitCapsCache just interprets the presence of a non-null private + * data pointer as a signal to skip cache validation. This prevents the + * cache from trying to validate the plugindir mtime, etc during test */ + virFileCacheSetPriv(driver->nbdkitCapsCache, GUINT_TO_POINTER(1)); + driver->xmlopt = virQEMUDriverCreateXMLConf(driver, "none"); if (!driver->xmlopt) goto error; @@ -885,6 +896,7 @@ testQemuInfoSetArgs(struct testQemuInfo *info, info->conf = conf; info->args.newargs = true; + info->args.fakeNbdkitCaps = qemuNbdkitCapsNew(TEST_NBDKIT_PATH); va_start(argptr, conf); while ((argname = va_arg(argptr, testQemuInfoArgName)) != ARG_END) { @@ -896,6 +908,13 @@ testQemuInfoSetArgs(struct testQemuInfo *info, virQEMUCapsSet(info->args.fakeCaps, flag); break; + case ARG_NBDKIT_CAPS: + info->args.fakeNbdkitCapsUsed = true; + + while ((flag = va_arg(argptr, int)) < QEMU_NBDKIT_CAPS_LAST) + qemuNbdkitCapsSet(info->args.fakeNbdkitCaps, flag); + break; + case ARG_GIC: info->args.gic = va_arg(argptr, int); break; @@ -1020,6 +1039,12 @@ testQemuInfoInitArgs(struct testQemuInfo *info) info->qemuCaps = g_steal_pointer(&info->args.fakeCaps); } + if (info->args.fakeNbdkitCapsUsed) + info->nbdkitCaps = g_steal_pointer(&info->args.fakeNbdkitCaps); + else + /* empty caps */ + info->nbdkitCaps = qemuNbdkitCapsNew(TEST_NBDKIT_PATH); + if (info->args.gic != GIC_NONE && testQemuCapsSetGIC(info->qemuCaps, info->args.gic) < 0) return -1; @@ -1037,6 +1062,8 @@ testQemuInfoClear(struct testQemuInfo *info) VIR_FREE(info->errfile); virObjectUnref(info->qemuCaps); g_clear_pointer(&info->args.fakeCaps, virObjectUnref); + g_clear_object(&info->nbdkitCaps); + g_clear_object(&info->args.fakeNbdkitCaps); } diff --git a/tests/testutilsqemu.h b/tests/testutilsqemu.h index 943958d02a..618837559c 100644 --- a/tests/testutilsqemu.h +++ b/tests/testutilsqemu.h @@ -28,6 +28,7 @@ # define TEST_TPM_ENV_VAR "VIR_TEST_MOCK_FAKE_TPM_VERSION" # define TPM_VER_1_2 "1.2" # define TPM_VER_2_0 "2.0" +# define TEST_NBDKIT_PATH "/usr/bin/nbdkit" enum { GIC_NONE = 0, @@ -52,6 +53,7 @@ typedef enum { ARG_CAPS_VER, ARG_CAPS_HOST_CPU_MODEL, ARG_HOST_OS, + ARG_NBDKIT_CAPS, ARG_END, } testQemuInfoArgName; @@ -82,6 +84,8 @@ struct testQemuArgs { bool newargs; virQEMUCaps *fakeCaps; bool fakeCapsUsed; + qemuNbdkitCaps *fakeNbdkitCaps; + bool fakeNbdkitCapsUsed; char *capsver; char *capsarch; qemuTestCPUDef capsHostCPUModel; @@ -96,6 +100,7 @@ struct testQemuInfo { char *outfile; char *errfile; virQEMUCaps *qemuCaps; + qemuNbdkitCaps *nbdkitCaps; const char *migrateFrom; int migrateFd; unsigned int flags; -- 2.37.3

On Thu, Oct 20, 2022 at 16:59:01 -0500, Jonathon Jongsma wrote:
Add new DO_TEST_CAPS_LATEST_NBDKIT macro to test xml2argv for various nbdkit capability scenarios.
Signed-off-by: Jonathon Jongsma <jjongsma@redhat.com> --- src/qemu/qemu_nbdkit.c | 20 +++++++++++++++++--- tests/qemuxml2argvtest.c | 11 +++++++++++ tests/testutilsqemu.c | 27 +++++++++++++++++++++++++++ tests/testutilsqemu.h | 5 +++++ 4 files changed, 60 insertions(+), 3 deletions(-)
diff --git a/tests/testutilsqemu.h b/tests/testutilsqemu.h index 943958d02a..618837559c 100644 --- a/tests/testutilsqemu.h +++ b/tests/testutilsqemu.h @@ -28,6 +28,7 @@ # define TEST_TPM_ENV_VAR "VIR_TEST_MOCK_FAKE_TPM_VERSION" # define TPM_VER_1_2 "1.2" # define TPM_VER_2_0 "2.0" +# define TEST_NBDKIT_PATH "/usr/bin/nbdkit"
Preferrably select some clearly fake path here so that if something tries to access it it fails. Reviewed-by: Peter Krempa <pkrempa@redhat.com>

This prepares encryption secrets and authentication secrets. When we add nbdkit-backed network storage sources, we will not need to send authentication secrets to qemu, since they will be sent to nbdkit instead. So split this into two different functions. Signed-off-by: Jonathon Jongsma <jjongsma@redhat.com> Reviewed-by: Peter Krempa <pkrempa@redhat.com> --- src/qemu/qemu_domain.c | 83 ++++++++++++++++++++++++++---------------- 1 file changed, 52 insertions(+), 31 deletions(-) diff --git a/src/qemu/qemu_domain.c b/src/qemu/qemu_domain.c index 3fca163415..ace7ae4c61 100644 --- a/src/qemu/qemu_domain.c +++ b/src/qemu/qemu_domain.c @@ -1299,24 +1299,19 @@ qemuDomainSecretStorageSourcePrepareCookies(qemuDomainObjPrivate *priv, /** - * qemuDomainSecretStorageSourcePrepare: + * qemuDomainSecretStorageSourcePrepareEncryption: * @priv: domain private object * @src: storage source struct to setup - * @authalias: prefix of the alias for secret holding authentication data - * @encalias: prefix of the alias for secret holding encryption password + * @alias: prefix of the alias for secret holding encryption password * - * Prepares data necessary for encryption and authentication of @src. The two - * alias prefixes are provided since in the backing chain authentication belongs - * to the storage protocol data whereas encryption is relevant to the format - * driver in qemu. The two will have different node names. + * Prepares data necessary for encryption of @src. * * Returns 0 on success; -1 on error while reporting an libvirt error. */ static int -qemuDomainSecretStorageSourcePrepare(qemuDomainObjPrivate *priv, - virStorageSource *src, - const char *aliasprotocol, - const char *aliasformat) +qemuDomainSecretStorageSourcePrepareEncryption(qemuDomainObjPrivate *priv, + virStorageSource *src, + const char *alias) { qemuDomainStorageSourcePrivate *srcPriv; bool hasEnc = src->encryption && src->encryption->nsecrets > 0; @@ -1324,13 +1319,43 @@ qemuDomainSecretStorageSourcePrepare(qemuDomainObjPrivate *priv, if (virStorageSourceIsEmpty(src)) return 0; - if (!src->auth && !hasEnc && src->ncookies == 0) + if (!hasEnc) return 0; - if (!(src->privateData = qemuDomainStorageSourcePrivateNew())) + srcPriv = qemuDomainStorageSourcePrivateFetch(src); + + if (!(srcPriv->encinfo = qemuDomainSecretInfoSetupFromSecret(priv, alias, + "encryption", + VIR_SECRET_USAGE_TYPE_VOLUME, + NULL, + &src->encryption->secrets[0]->seclookupdef))) return -1; - srcPriv = QEMU_DOMAIN_STORAGE_SOURCE_PRIVATE(src); + return 0; +} + + +/** + * qemuDomainSecretStorageSourcePrepareAuth: + * @priv: domain private object + * @src: storage source struct to setup + * @alias: prefix of the alias for secret holding authentication data + * + * Prepares data necessary for authentication of @src. + * + * Returns 0 on success; -1 on error while reporting an libvirt error. + */ +static int +qemuDomainSecretStorageSourcePrepareAuth(qemuDomainObjPrivate *priv, + virStorageSource *src, + const char *alias) +{ + qemuDomainStorageSourcePrivate *srcPriv; + + if (virStorageSourceIsEmpty(src)) + return 0; + + srcPriv = qemuDomainStorageSourcePrivateFetch(src); if (src->auth) { virSecretUsageType usageType = VIR_SECRET_USAGE_TYPE_ISCSI; @@ -1338,7 +1363,7 @@ qemuDomainSecretStorageSourcePrepare(qemuDomainObjPrivate *priv, if (src->protocol == VIR_STORAGE_NET_PROTOCOL_RBD) usageType = VIR_SECRET_USAGE_TYPE_CEPH; - if (!(srcPriv->secinfo = qemuDomainSecretInfoSetupFromSecret(priv, aliasprotocol, + if (!(srcPriv->secinfo = qemuDomainSecretInfoSetupFromSecret(priv, alias, "auth", usageType, src->auth->username, @@ -1346,19 +1371,10 @@ qemuDomainSecretStorageSourcePrepare(qemuDomainObjPrivate *priv, return -1; } - if (hasEnc) { - if (!(srcPriv->encinfo = qemuDomainSecretInfoSetupFromSecret(priv, aliasformat, - "encryption", - VIR_SECRET_USAGE_TYPE_VOLUME, - NULL, - &src->encryption->secrets[0]->seclookupdef))) - return -1; - } - if (src->ncookies && !(srcPriv->httpcookie = qemuDomainSecretStorageSourcePrepareCookies(priv, src, - aliasprotocol))) + alias))) return -1; return 0; @@ -10805,9 +10821,12 @@ qemuDomainPrepareDiskSourceLegacy(virDomainDiskDef *disk, qemuDomainPrepareStorageSourceConfig(disk->src, cfg); qemuDomainPrepareDiskSourceData(disk, disk->src); - if (qemuDomainSecretStorageSourcePrepare(priv, disk->src, - disk->info.alias, - disk->info.alias) < 0) + if (qemuDomainSecretStorageSourcePrepareEncryption(priv, disk->src, + disk->info.alias) < 0) + return -1; + + if (qemuDomainSecretStorageSourcePrepareAuth(priv, disk->src, + disk->info.alias) < 0) return -1; if (qemuDomainPrepareStorageSourcePR(disk->src, priv, disk->info.alias) < 0) @@ -10843,9 +10862,11 @@ qemuDomainPrepareStorageSourceBlockdevNodename(virDomainDiskDef *disk, qemuDomainPrepareStorageSourceConfig(src, cfg); qemuDomainPrepareDiskSourceData(disk, src); - if (qemuDomainSecretStorageSourcePrepare(priv, src, - src->nodestorage, - src->nodeformat) < 0) + if (qemuDomainSecretStorageSourcePrepareEncryption(priv, src, + src->nodeformat) < 0) + return -1; + if (qemuDomainSecretStorageSourcePrepareAuth(priv, src, + src->nodestorage) < 0) return -1; if (qemuDomainPrepareStorageSourcePR(src, priv, src->nodestorage) < 0) -- 2.37.3

Add xml to the private data for a disk source to represent the nbdkit process so that the state can be re-created if the libvirt daemon is restarted. Format: <nbdkit> <pidfile>/path/to/nbdkit.pid</pidfile> <socketfile>/path/to/nbdkit.socket</socketfile> </nbdkit> Signed-off-by: Jonathon Jongsma <jjongsma@redhat.com> --- src/qemu/qemu_domain.c | 53 ++++++++++++++++++++++++++++++++++++++++++ src/qemu/qemu_nbdkit.c | 21 +++++++++++++++++ src/qemu/qemu_nbdkit.h | 5 ++++ 3 files changed, 79 insertions(+) diff --git a/src/qemu/qemu_domain.c b/src/qemu/qemu_domain.c index ace7ae4c61..2ae87392cb 100644 --- a/src/qemu/qemu_domain.c +++ b/src/qemu/qemu_domain.c @@ -1849,6 +1849,34 @@ qemuStorageSourcePrivateDataAssignSecinfo(qemuDomainSecretInfo **secinfo, } +static int +qemuStorageSourcePrivateDataParseNbdkit(xmlNodePtr node, + xmlXPathContextPtr ctxt, + virStorageSource *src) +{ + qemuDomainStorageSourcePrivate *srcpriv = qemuDomainStorageSourcePrivateFetch(src); + g_autofree char *pidfile = NULL; + g_autofree char *socketfile = NULL; + VIR_XPATH_NODE_AUTORESTORE(ctxt); + + if (srcpriv->nbdkitProcess) + return 0; + + ctxt->node = node; + + if (!(pidfile = virXPathString("string(./pidfile)", ctxt))) + return -1; + + if (!(socketfile = virXPathString("string(./socketfile)", ctxt))) + return -1; + + if (!(srcpriv->nbdkitProcess = qemuNbdkitProcessLoad(src, pidfile, socketfile))) + return -1; + + return 0; +} + + static int qemuStorageSourcePrivateDataParse(xmlXPathContextPtr ctxt, virStorageSource *src) @@ -1859,6 +1887,7 @@ qemuStorageSourcePrivateDataParse(xmlXPathContextPtr ctxt, g_autofree char *httpcookiealias = NULL; g_autofree char *tlskeyalias = NULL; g_autofree char *thresholdEventWithIndex = NULL; + xmlNodePtr nbdkitnode = NULL; src->nodestorage = virXPathString("string(./nodenames/nodename[@type='storage']/@name)", ctxt); src->nodeformat = virXPathString("string(./nodenames/nodename[@type='format']/@name)", ctxt); @@ -1902,6 +1931,10 @@ qemuStorageSourcePrivateDataParse(xmlXPathContextPtr ctxt, virTristateBoolTypeFromString(thresholdEventWithIndex) == VIR_TRISTATE_BOOL_YES) src->thresholdEventWithIndex = true; + if ((nbdkitnode = virXPathNode("nbdkit", ctxt))) { + if (qemuStorageSourcePrivateDataParseNbdkit(nbdkitnode, ctxt, src) < 0) + return -1; + } return 0; } @@ -1919,6 +1952,23 @@ qemuStorageSourcePrivateDataFormatSecinfo(virBuffer *buf, } +static void +qemuStorageSourcePrivateDataFormatNbdkit(qemuNbdkitProcess *nbdkit, + virBuffer *buf) +{ + g_auto(virBuffer) childBuf = VIR_BUFFER_INIT_CHILD(buf); + + if (!nbdkit) + return; + + virBufferEscapeString(&childBuf, "<pidfile>%s</pidfile>\n", + nbdkit->pidfile); + virBufferEscapeString(&childBuf, "<socketfile>%s</socketfile>\n", + nbdkit->socketfile); + virXMLFormatElement(buf, "nbdkit", NULL, &childBuf); +} + + static int qemuStorageSourcePrivateDataFormat(virStorageSource *src, virBuffer *buf) @@ -1957,6 +2007,9 @@ qemuStorageSourcePrivateDataFormat(virStorageSource *src, if (src->thresholdEventWithIndex) virBufferAddLit(buf, "<thresholdEvent indexUsed='yes'/>\n"); + if (srcPriv) + qemuStorageSourcePrivateDataFormatNbdkit(srcPriv->nbdkitProcess, buf); + return 0; } diff --git a/src/qemu/qemu_nbdkit.c b/src/qemu/qemu_nbdkit.c index 59c452a15e..5b47a15112 100644 --- a/src/qemu/qemu_nbdkit.c +++ b/src/qemu/qemu_nbdkit.c @@ -597,6 +597,27 @@ qemuNbdkitProcessNew(virStorageSource *source, } +qemuNbdkitProcess * +qemuNbdkitProcessLoad(virStorageSource *source, + const char *pidfile, + const char *socketfile) +{ + int rc; + g_autoptr(qemuNbdkitProcess) nbdkit = qemuNbdkitProcessNew(source, pidfile, socketfile); + + if ((rc = virPidFileReadPath(nbdkit->pidfile, &nbdkit->pid)) < 0) + return NULL; + + if (virProcessKill(nbdkit->pid, 0) < 0) { + virReportError(VIR_ERR_INTERNAL_ERROR, + _("nbdkit process %i is not alive"), nbdkit->pid); + return NULL; + } + + return g_steal_pointer(&nbdkit); +} + + bool qemuNbdkitInitStorageSource(qemuNbdkitCaps *caps, virStorageSource *source, diff --git a/src/qemu/qemu_nbdkit.h b/src/qemu/qemu_nbdkit.h index 30cab744b0..ca7f1dcf31 100644 --- a/src/qemu/qemu_nbdkit.h +++ b/src/qemu/qemu_nbdkit.h @@ -89,4 +89,9 @@ qemuNbdkitProcessStop(qemuNbdkitProcess *proc); void qemuNbdkitProcessFree(qemuNbdkitProcess *proc); +qemuNbdkitProcess * +qemuNbdkitProcessLoad(virStorageSource *source, + const char *pidfile, + const char *socketfile); + G_DEFINE_AUTOPTR_CLEANUP_FUNC(qemuNbdkitProcess, qemuNbdkitProcessFree); -- 2.37.3

On Thu, Oct 20, 2022 at 16:59:03 -0500, Jonathon Jongsma wrote:
Add xml to the private data for a disk source to represent the nbdkit process so that the state can be re-created if the libvirt daemon is restarted. Format:
<nbdkit> <pidfile>/path/to/nbdkit.pid</pidfile> <socketfile>/path/to/nbdkit.socket</socketfile> </nbdkit>
Signed-off-by: Jonathon Jongsma <jjongsma@redhat.com> --- src/qemu/qemu_domain.c | 53 ++++++++++++++++++++++++++++++++++++++++++ src/qemu/qemu_nbdkit.c | 21 +++++++++++++++++ src/qemu/qemu_nbdkit.h | 5 ++++ 3 files changed, 79 insertions(+)
diff --git a/src/qemu/qemu_domain.c b/src/qemu/qemu_domain.c index ace7ae4c61..2ae87392cb 100644 --- a/src/qemu/qemu_domain.c +++ b/src/qemu/qemu_domain.c @@ -1849,6 +1849,34 @@ qemuStorageSourcePrivateDataAssignSecinfo(qemuDomainSecretInfo **secinfo, }
+static int +qemuStorageSourcePrivateDataParseNbdkit(xmlNodePtr node, + xmlXPathContextPtr ctxt, + virStorageSource *src) +{ + qemuDomainStorageSourcePrivate *srcpriv = qemuDomainStorageSourcePrivateFetch(src); + g_autofree char *pidfile = NULL; + g_autofree char *socketfile = NULL; + VIR_XPATH_NODE_AUTORESTORE(ctxt); + + if (srcpriv->nbdkitProcess) + return 0; + + ctxt->node = node; + + if (!(pidfile = virXPathString("string(./pidfile)", ctxt))) + return -1; + + if (!(socketfile = virXPathString("string(./socketfile)", ctxt))) + return -1; + + if (!(srcpriv->nbdkitProcess = qemuNbdkitProcessLoad(src, pidfile, socketfile))) + return -1;
Per implementation below this actually checks that the nbdkit process is alive. That is not an operation that would be allowed in any parser code. Any failure of that results in the status XML file being unparsable and thus failing to recover state of the VM. Not even checking existance of the pidfile is allowed in the parser. All those steps must be moved to the point when we are reconnecting to qemu. It is obvious once you add test data into qemustatusxml2xmltest: diff --git a/tests/qemustatusxml2xmldata/modern-in.xml b/tests/qemustatusxml2xmldata/modern-in.xml index 7759034f7a..71b3eb4736 100644 --- a/tests/qemustatusxml2xmldata/modern-in.xml +++ b/tests/qemustatusxml2xmldata/modern-in.xml @@ -342,6 +342,10 @@ <TLSx509 alias='transport-alias'/> </objects> <thresholdEvent indexUsed='yes'/> + <nbdkit> + <pidfile>/path/to/nbdkit.pid</pidfile> + <socketfile>/path/to/nbdkit.socket</socketfile> + </nbdkit> </privateData> </source> <backingStore/>

For virStorageSource objects that contain an nbdkitProcess, start that nbdkit process to serve that network drive and then pass the nbdkit socket to qemu rather than sending the network url to qemu directly. Signed-off-by: Jonathon Jongsma <jjongsma@redhat.com> --- src/qemu/qemu_block.c | 162 +++++++++++------- src/qemu/qemu_domain.c | 21 ++- src/qemu/qemu_extdevice.c | 48 ++++++ src/qemu/qemu_nbdkit.c | 63 +++++++ src/qemu/qemu_nbdkit.h | 13 ++ ...sk-cdrom-network-nbdkit.x86_64-latest.args | 42 +++++ .../disk-cdrom-network-nbdkit.xml | 1 + ...isk-network-http-nbdkit.x86_64-latest.args | 45 +++++ .../disk-network-http-nbdkit.xml | 1 + ...rce-curl-nbdkit-backing.x86_64-latest.args | 38 ++++ ...isk-network-source-curl-nbdkit-backing.xml | 45 +++++ ...work-source-curl-nbdkit.x86_64-latest.args | 50 ++++++ .../disk-network-source-curl-nbdkit.xml | 1 + ...isk-network-source-curl.x86_64-latest.args | 53 ++++++ .../disk-network-source-curl.xml | 71 ++++++++ ...disk-network-ssh-nbdkit.x86_64-latest.args | 36 ++++ .../disk-network-ssh-nbdkit.xml | 1 + tests/qemuxml2argvtest.c | 6 + 18 files changed, 627 insertions(+), 70 deletions(-) create mode 100644 tests/qemuxml2argvdata/disk-cdrom-network-nbdkit.x86_64-latest.args create mode 120000 tests/qemuxml2argvdata/disk-cdrom-network-nbdkit.xml create mode 100644 tests/qemuxml2argvdata/disk-network-http-nbdkit.x86_64-latest.args create mode 120000 tests/qemuxml2argvdata/disk-network-http-nbdkit.xml create mode 100644 tests/qemuxml2argvdata/disk-network-source-curl-nbdkit-backing.x86_64-latest.args create mode 100644 tests/qemuxml2argvdata/disk-network-source-curl-nbdkit-backing.xml create mode 100644 tests/qemuxml2argvdata/disk-network-source-curl-nbdkit.x86_64-latest.args create mode 120000 tests/qemuxml2argvdata/disk-network-source-curl-nbdkit.xml create mode 100644 tests/qemuxml2argvdata/disk-network-source-curl.x86_64-latest.args create mode 100644 tests/qemuxml2argvdata/disk-network-source-curl.xml create mode 100644 tests/qemuxml2argvdata/disk-network-ssh-nbdkit.x86_64-latest.args create mode 120000 tests/qemuxml2argvdata/disk-network-ssh-nbdkit.xml diff --git a/src/qemu/qemu_block.c b/src/qemu/qemu_block.c index b82e3311e1..224d468799 100644 --- a/src/qemu/qemu_block.c +++ b/src/qemu/qemu_block.c @@ -439,6 +439,32 @@ qemuBlockStorageSourceGetCURLProps(virStorageSource *src, } +static virJSONValue * +qemuBlockStorageSourceGetNbdkitProps(virStorageSource *src) +{ + qemuDomainStorageSourcePrivate *srcPriv = QEMU_DOMAIN_STORAGE_SOURCE_PRIVATE(src); + virJSONValue *ret = NULL; + g_autoptr(virJSONValue) serverprops = NULL; + virStorageNetHostDef host = { .transport = VIR_STORAGE_NET_HOST_TRANS_UNIX }; + + /* srcPriv->nbdkitProcess will already be initialized if we can use nbdkit + * to proxy this storage source */ + if (!(srcPriv && srcPriv->nbdkitProcess)) + return NULL; + + host.socket = srcPriv->nbdkitProcess->socketfile; + serverprops = qemuBlockStorageSourceBuildJSONSocketAddress(&host); + + if (!serverprops) + return NULL; + + if (virJSONValueObjectAdd(&ret, "a:server", &serverprops, NULL) < 0) + return NULL; + + return ret; +} + + static virJSONValue * qemuBlockStorageSourceGetISCSIProps(virStorageSource *src, bool onlytarget) @@ -851,69 +877,75 @@ qemuBlockStorageSourceGetBackendProps(virStorageSource *src, return NULL; case VIR_STORAGE_TYPE_NETWORK: - switch ((virStorageNetProtocol) src->protocol) { - case VIR_STORAGE_NET_PROTOCOL_GLUSTER: - driver = "gluster"; - if (!(fileprops = qemuBlockStorageSourceGetGlusterProps(src, onlytarget))) - return NULL; - break; - - case VIR_STORAGE_NET_PROTOCOL_VXHS: - driver = "vxhs"; - if (!(fileprops = qemuBlockStorageSourceGetVxHSProps(src, onlytarget))) - return NULL; - break; - - case VIR_STORAGE_NET_PROTOCOL_HTTP: - case VIR_STORAGE_NET_PROTOCOL_HTTPS: - case VIR_STORAGE_NET_PROTOCOL_FTP: - case VIR_STORAGE_NET_PROTOCOL_FTPS: - case VIR_STORAGE_NET_PROTOCOL_TFTP: - driver = virStorageNetProtocolTypeToString(src->protocol); - if (!(fileprops = qemuBlockStorageSourceGetCURLProps(src, onlytarget))) - return NULL; - break; - - case VIR_STORAGE_NET_PROTOCOL_ISCSI: - driver = "iscsi"; - if (!(fileprops = qemuBlockStorageSourceGetISCSIProps(src, onlytarget))) - return NULL; - break; - - case VIR_STORAGE_NET_PROTOCOL_NBD: + /* prefer using nbdkit for sources that are supported */ + if ((fileprops = qemuBlockStorageSourceGetNbdkitProps(src))) { driver = "nbd"; - if (!(fileprops = qemuBlockStorageSourceGetNBDProps(src, onlytarget))) - return NULL; - break; - - case VIR_STORAGE_NET_PROTOCOL_RBD: - driver = "rbd"; - if (!(fileprops = qemuBlockStorageSourceGetRBDProps(src, onlytarget))) - return NULL; - break; - - case VIR_STORAGE_NET_PROTOCOL_SHEEPDOG: - driver = "sheepdog"; - if (!(fileprops = qemuBlockStorageSourceGetSheepdogProps(src))) - return NULL; - break; - - case VIR_STORAGE_NET_PROTOCOL_SSH: - driver = "ssh"; - if (!(fileprops = qemuBlockStorageSourceGetSshProps(src))) - return NULL; break; - - case VIR_STORAGE_NET_PROTOCOL_NFS: - driver = "nfs"; - if (!(fileprops = qemuBlockStorageSourceGetNFSProps(src))) - return NULL; - break; - - case VIR_STORAGE_NET_PROTOCOL_NONE: - case VIR_STORAGE_NET_PROTOCOL_LAST: - virReportEnumRangeError(virStorageNetProtocol, src->protocol); - return NULL; + } else { + switch ((virStorageNetProtocol) src->protocol) { + case VIR_STORAGE_NET_PROTOCOL_GLUSTER: + driver = "gluster"; + if (!(fileprops = qemuBlockStorageSourceGetGlusterProps(src, onlytarget))) + return NULL; + break; + + case VIR_STORAGE_NET_PROTOCOL_VXHS: + driver = "vxhs"; + if (!(fileprops = qemuBlockStorageSourceGetVxHSProps(src, onlytarget))) + return NULL; + break; + + case VIR_STORAGE_NET_PROTOCOL_HTTP: + case VIR_STORAGE_NET_PROTOCOL_HTTPS: + case VIR_STORAGE_NET_PROTOCOL_FTP: + case VIR_STORAGE_NET_PROTOCOL_FTPS: + case VIR_STORAGE_NET_PROTOCOL_TFTP: + driver = virStorageNetProtocolTypeToString(src->protocol); + if (!(fileprops = qemuBlockStorageSourceGetCURLProps(src, onlytarget))) + return NULL; + break; + + case VIR_STORAGE_NET_PROTOCOL_ISCSI: + driver = "iscsi"; + if (!(fileprops = qemuBlockStorageSourceGetISCSIProps(src, onlytarget))) + return NULL; + break; + + case VIR_STORAGE_NET_PROTOCOL_NBD: + driver = "nbd"; + if (!(fileprops = qemuBlockStorageSourceGetNBDProps(src, onlytarget))) + return NULL; + break; + + case VIR_STORAGE_NET_PROTOCOL_RBD: + driver = "rbd"; + if (!(fileprops = qemuBlockStorageSourceGetRBDProps(src, onlytarget))) + return NULL; + break; + + case VIR_STORAGE_NET_PROTOCOL_SHEEPDOG: + driver = "sheepdog"; + if (!(fileprops = qemuBlockStorageSourceGetSheepdogProps(src))) + return NULL; + break; + + case VIR_STORAGE_NET_PROTOCOL_SSH: + driver = "ssh"; + if (!(fileprops = qemuBlockStorageSourceGetSshProps(src))) + return NULL; + break; + + case VIR_STORAGE_NET_PROTOCOL_NFS: + driver = "nfs"; + if (!(fileprops = qemuBlockStorageSourceGetNFSProps(src))) + return NULL; + break; + + case VIR_STORAGE_NET_PROTOCOL_NONE: + case VIR_STORAGE_NET_PROTOCOL_LAST: + virReportEnumRangeError(virStorageNetProtocol, src->protocol); + return NULL; + } } break; } @@ -2196,6 +2228,7 @@ qemuBlockStorageSourceCreateGetStorageProps(virStorageSource *src, g_autoptr(virJSONValue) location = NULL; const char *driver = NULL; const char *filename = NULL; + qemuDomainStorageSourcePrivate *srcPriv = QEMU_DOMAIN_STORAGE_SOURCE_PRIVATE(src); switch (actualType) { case VIR_STORAGE_TYPE_FILE: @@ -2224,6 +2257,13 @@ qemuBlockStorageSourceCreateGetStorageProps(virStorageSource *src, break; case VIR_STORAGE_NET_PROTOCOL_SSH: + if (srcPriv->nbdkitProcess) { + /* disk creation not yet supported with nbdkit, and even if it + * was supported, it would not be done with blockdev-create + * props */ + return 0; + } + driver = "ssh"; if (!(location = qemuBlockStorageSourceGetSshProps(src))) return -1; diff --git a/src/qemu/qemu_domain.c b/src/qemu/qemu_domain.c index 2ae87392cb..a3fafdd9e3 100644 --- a/src/qemu/qemu_domain.c +++ b/src/qemu/qemu_domain.c @@ -10918,21 +10918,24 @@ qemuDomainPrepareStorageSourceBlockdevNodename(virDomainDiskDef *disk, if (qemuDomainSecretStorageSourcePrepareEncryption(priv, src, src->nodeformat) < 0) return -1; - if (qemuDomainSecretStorageSourcePrepareAuth(priv, src, - src->nodestorage) < 0) - return -1; if (qemuDomainPrepareStorageSourcePR(src, priv, src->nodestorage) < 0) return -1; - if (qemuDomainPrepareStorageSourceTLS(src, cfg, src->nodestorage, - priv) < 0) - return -1; + if (!qemuDomainPrepareStorageSourceNbdkit(src, cfg, src->nodestorage, priv)) { + /* If we're using nbdkit to serve the storage source, we don't pass + * authentication secrets to qemu, but will pass them to nbdkit instead */ + if (qemuDomainSecretStorageSourcePrepareAuth(priv, src, + src->nodestorage) < 0) + return -1; - if (qemuDomainPrepareStorageSourceNFS(src) < 0) - return -1; + if (qemuDomainPrepareStorageSourceTLS(src, cfg, src->nodestorage, + priv) < 0) + return -1; - qemuDomainPrepareStorageSourceNbdkit(src, cfg, src->nodestorage, priv); + if (qemuDomainPrepareStorageSourceNFS(src) < 0) + return -1; + } return 0; } diff --git a/src/qemu/qemu_extdevice.c b/src/qemu/qemu_extdevice.c index 24a57b0f74..4a3d082f1e 100644 --- a/src/qemu/qemu_extdevice.c +++ b/src/qemu/qemu_extdevice.c @@ -219,6 +219,17 @@ qemuExtDevicesStart(virQEMUDriver *driver, return -1; } + for (i = 0; i < def->ndisks; i++) { + virDomainDiskDef *disk = def->disks[i]; + if (qemuNbdkitStartStorageSource(driver, vm, disk->src) < 0) + return -1; + } + + if (def->os.loader && def->os.loader->nvram) { + if (qemuNbdkitStartStorageSource(driver, vm, def->os.loader->nvram) < 0) + return -1; + } + return 0; } @@ -263,6 +274,14 @@ qemuExtDevicesStop(virQEMUDriver *driver, fs->fsdriver == VIR_DOMAIN_FS_DRIVER_TYPE_VIRTIOFS) qemuVirtioFSStop(driver, vm, fs); } + + for (i = 0; i < def->ndisks; i++) { + virDomainDiskDef *disk = def->disks[i]; + qemuNbdkitStopStorageSource(disk->src); + } + + if (def->os.loader && def->os.loader->nvram) + qemuNbdkitStopStorageSource(def->os.loader->nvram); } @@ -292,6 +311,24 @@ qemuExtDevicesHasDevice(virDomainDef *def) } +/* recursively setup nbdkit cgroups for backing chain of src */ +static int qemuExtDevicesSetupCgroupNbdkit(virStorageSource *src, + virCgroup *cgroup) +{ + qemuDomainStorageSourcePrivate *priv = QEMU_DOMAIN_STORAGE_SOURCE_PRIVATE(src); + + if (src->backingStore) + if (qemuExtDevicesSetupCgroupNbdkit(src->backingStore, cgroup) < 0) + return -1; + + if (priv && priv->nbdkitProcess && + qemuNbdkitProcessSetupCgroup(priv->nbdkitProcess, cgroup) < 0) + return -1; + + return 0; +} + + int qemuExtDevicesSetupCgroup(virQEMUDriver *driver, virDomainObj *vm, @@ -325,6 +362,17 @@ qemuExtDevicesSetupCgroup(virQEMUDriver *driver, return -1; } + for (i = 0; i < def->ndisks; i++) { + virDomainDiskDef *disk = def->disks[i]; + if (qemuExtDevicesSetupCgroupNbdkit(disk->src, cgroup) < 0) + return -1; + } + + if (def->os.loader && def->os.loader->nvram) { + if (qemuExtDevicesSetupCgroupNbdkit(def->os.loader->nvram, cgroup) < 0) + return -1; + } + for (i = 0; i < def->nfss; i++) { virDomainFSDef *fs = def->fss[i]; diff --git a/src/qemu/qemu_nbdkit.c b/src/qemu/qemu_nbdkit.c index 5b47a15112..8cb9e0e604 100644 --- a/src/qemu/qemu_nbdkit.c +++ b/src/qemu/qemu_nbdkit.c @@ -672,6 +672,61 @@ qemuNbdkitInitStorageSource(qemuNbdkitCaps *caps, } +static int +qemuNbdkitStartStorageSourceOne(virQEMUDriver *driver, + virDomainObj *vm, + virStorageSource *src) +{ + qemuDomainStorageSourcePrivate *priv = QEMU_DOMAIN_STORAGE_SOURCE_PRIVATE(src); + + if (priv && priv->nbdkitProcess && + qemuNbdkitProcessStart(priv->nbdkitProcess, vm, driver) < 0) + return -1; + + return 0; +} + + +/* recursively start nbdkit for backing chain of src */ +int +qemuNbdkitStartStorageSource(virQEMUDriver *driver, + virDomainObj *vm, + virStorageSource *src) +{ + virStorageSource *backing; + + for (backing = src->backingStore; backing != NULL; backing = backing->backingStore) + if (qemuNbdkitStartStorageSourceOne(driver, vm, backing) < 0) + return -1; + + return qemuNbdkitStartStorageSourceOne(driver, vm, src); +} + + +static void +qemuNbdkitStopStorageSourceOne(virStorageSource *src) +{ + qemuDomainStorageSourcePrivate *priv = QEMU_DOMAIN_STORAGE_SOURCE_PRIVATE(src); + + if (priv && priv->nbdkitProcess && + qemuNbdkitProcessStop(priv->nbdkitProcess) < 0) + VIR_WARN("Unable to stop nbdkit for storage source '%s'", src->nodestorage); +} + + +/* recursively stop nbdkit processes for backing chain of src */ +void +qemuNbdkitStopStorageSource(virStorageSource *src) +{ + virStorageSource *backing; + + qemuNbdkitStopStorageSourceOne(src); + + for (backing = src->backingStore; backing != NULL; backing = backing->backingStore) + qemuNbdkitStopStorageSourceOne(backing); +} + + static int qemuNbdkitProcessBuildCommandCurl(qemuNbdkitProcess *proc, virCommand *cmd) @@ -839,6 +894,14 @@ qemuNbdkitProcessFree(qemuNbdkitProcess *proc) } +int +qemuNbdkitProcessSetupCgroup(qemuNbdkitProcess *proc, + virCgroup *cgroup) +{ + return virCgroupAddProcess(cgroup, proc->pid); +} + + int qemuNbdkitProcessStart(qemuNbdkitProcess *proc, virDomainObj *vm, diff --git a/src/qemu/qemu_nbdkit.h b/src/qemu/qemu_nbdkit.h index ca7f1dcf31..c9af6efcfa 100644 --- a/src/qemu/qemu_nbdkit.h +++ b/src/qemu/qemu_nbdkit.h @@ -23,6 +23,7 @@ #include "internal.h" #include "storage_source_conf.h" +#include "vircgroup.h" #include "virenum.h" #include "virfilecache.h" @@ -56,6 +57,14 @@ qemuNbdkitInitStorageSource(qemuNbdkitCaps *nbdkitCaps, uid_t user, gid_t group); +int +qemuNbdkitStartStorageSource(virQEMUDriver *driver, + virDomainObj *vm, + virStorageSource *src); + +void +qemuNbdkitStopStorageSource(virStorageSource *src); + bool qemuNbdkitCapsGet(qemuNbdkitCaps *nbdkitCaps, qemuNbdkitCapsFlags flag); @@ -89,6 +98,10 @@ qemuNbdkitProcessStop(qemuNbdkitProcess *proc); void qemuNbdkitProcessFree(qemuNbdkitProcess *proc); +int +qemuNbdkitProcessSetupCgroup(qemuNbdkitProcess *proc, + virCgroup *cgroup); + qemuNbdkitProcess * qemuNbdkitProcessLoad(virStorageSource *source, const char *pidfile, diff --git a/tests/qemuxml2argvdata/disk-cdrom-network-nbdkit.x86_64-latest.args b/tests/qemuxml2argvdata/disk-cdrom-network-nbdkit.x86_64-latest.args new file mode 100644 index 0000000000..eec7ef2af7 --- /dev/null +++ b/tests/qemuxml2argvdata/disk-cdrom-network-nbdkit.x86_64-latest.args @@ -0,0 +1,42 @@ +LC_ALL=C \ +PATH=/bin \ +HOME=/tmp/lib/domain--1-QEMUGuest1 \ +USER=test \ +LOGNAME=test \ +XDG_DATA_HOME=/tmp/lib/domain--1-QEMUGuest1/.local/share \ +XDG_CACHE_HOME=/tmp/lib/domain--1-QEMUGuest1/.cache \ +XDG_CONFIG_HOME=/tmp/lib/domain--1-QEMUGuest1/.config \ +/usr/bin/qemu-system-x86_64 \ +-name guest=QEMUGuest1,debug-threads=on \ +-S \ +-object '{"qom-type":"secret","id":"masterKey0","format":"raw","file":"/tmp/lib/domain--1-QEMUGuest1/master-key.aes"}' \ +-machine pc,usb=off,dump-guest-core=off,memory-backend=pc.ram \ +-accel kvm \ +-cpu qemu64 \ +-m 1024 \ +-object '{"qom-type":"memory-backend-ram","id":"pc.ram","size":1073741824}' \ +-overcommit mem-lock=off \ +-smp 1,sockets=1,cores=1,threads=1 \ +-uuid c7a5fdbd-edaf-9455-926a-d65c16db1809 \ +-display none \ +-no-user-config \ +-nodefaults \ +-chardev socket,id=charmonitor,fd=1729,server=on,wait=off \ +-mon chardev=charmonitor,id=monitor,mode=control \ +-rtc base=utc \ +-no-shutdown \ +-boot strict=on \ +-device '{"driver":"piix3-usb-uhci","id":"usb","bus":"pci.0","addr":"0x1.0x2"}' \ +-blockdev '{"driver":"nbd","server":{"type":"unix","path":"/tmp/lib/domain--1-QEMUGuest1/nbdkit-libvirt-3-storage.socket"},"node-name":"libvirt-3-storage","auto-read-only":true,"discard":"unmap"}' \ +-blockdev '{"node-name":"libvirt-3-format","read-only":true,"driver":"raw","file":"libvirt-3-storage"}' \ +-device '{"driver":"ide-cd","bus":"ide.0","unit":0,"drive":"libvirt-3-format","id":"ide0-0-0","bootindex":1}' \ +-blockdev '{"driver":"nbd","server":{"type":"unix","path":"/tmp/lib/domain--1-QEMUGuest1/nbdkit-libvirt-2-storage.socket"},"node-name":"libvirt-2-storage","auto-read-only":true,"discard":"unmap"}' \ +-blockdev '{"node-name":"libvirt-2-format","read-only":true,"driver":"raw","file":"libvirt-2-storage"}' \ +-device '{"driver":"ide-cd","bus":"ide.0","unit":1,"drive":"libvirt-2-format","id":"ide0-0-1"}' \ +-blockdev '{"driver":"nbd","server":{"type":"unix","path":"/tmp/lib/domain--1-QEMUGuest1/nbdkit-libvirt-1-storage.socket"},"node-name":"libvirt-1-storage","auto-read-only":true,"discard":"unmap"}' \ +-blockdev '{"node-name":"libvirt-1-format","read-only":true,"driver":"raw","file":"libvirt-1-storage"}' \ +-device '{"driver":"ide-cd","bus":"ide.1","unit":0,"drive":"libvirt-1-format","id":"ide0-1-0"}' \ +-audiodev '{"id":"audio1","driver":"none"}' \ +-device '{"driver":"virtio-balloon-pci","id":"balloon0","bus":"pci.0","addr":"0x2"}' \ +-sandbox on,obsolete=deny,elevateprivileges=deny,spawn=deny,resourcecontrol=deny \ +-msg timestamp=on diff --git a/tests/qemuxml2argvdata/disk-cdrom-network-nbdkit.xml b/tests/qemuxml2argvdata/disk-cdrom-network-nbdkit.xml new file mode 120000 index 0000000000..55f677546f --- /dev/null +++ b/tests/qemuxml2argvdata/disk-cdrom-network-nbdkit.xml @@ -0,0 +1 @@ +disk-cdrom-network.xml \ No newline at end of file diff --git a/tests/qemuxml2argvdata/disk-network-http-nbdkit.x86_64-latest.args b/tests/qemuxml2argvdata/disk-network-http-nbdkit.x86_64-latest.args new file mode 100644 index 0000000000..25d476d3ce --- /dev/null +++ b/tests/qemuxml2argvdata/disk-network-http-nbdkit.x86_64-latest.args @@ -0,0 +1,45 @@ +LC_ALL=C \ +PATH=/bin \ +HOME=/tmp/lib/domain--1-QEMUGuest1 \ +USER=test \ +LOGNAME=test \ +XDG_DATA_HOME=/tmp/lib/domain--1-QEMUGuest1/.local/share \ +XDG_CACHE_HOME=/tmp/lib/domain--1-QEMUGuest1/.cache \ +XDG_CONFIG_HOME=/tmp/lib/domain--1-QEMUGuest1/.config \ +/usr/bin/qemu-system-x86_64 \ +-name guest=QEMUGuest1,debug-threads=on \ +-S \ +-object '{"qom-type":"secret","id":"masterKey0","format":"raw","file":"/tmp/lib/domain--1-QEMUGuest1/master-key.aes"}' \ +-machine pc,usb=off,dump-guest-core=off,memory-backend=pc.ram \ +-accel kvm \ +-cpu qemu64 \ +-m 214 \ +-object '{"qom-type":"memory-backend-ram","id":"pc.ram","size":224395264}' \ +-overcommit mem-lock=off \ +-smp 1,sockets=1,cores=1,threads=1 \ +-uuid c7a5fdbd-edaf-9455-926a-d65c16db1809 \ +-display none \ +-no-user-config \ +-nodefaults \ +-chardev socket,id=charmonitor,fd=1729,server=on,wait=off \ +-mon chardev=charmonitor,id=monitor,mode=control \ +-rtc base=utc \ +-no-shutdown \ +-no-acpi \ +-boot strict=on \ +-device '{"driver":"piix3-usb-uhci","id":"usb","bus":"pci.0","addr":"0x1.0x2"}' \ +-blockdev '{"driver":"nbd","server":{"type":"unix","path":"/tmp/lib/domain--1-QEMUGuest1/nbdkit-libvirt-4-storage.socket"},"node-name":"libvirt-4-storage","auto-read-only":true,"discard":"unmap"}' \ +-blockdev '{"node-name":"libvirt-4-format","read-only":false,"driver":"raw","file":"libvirt-4-storage"}' \ +-device '{"driver":"virtio-blk-pci","bus":"pci.0","addr":"0x2","drive":"libvirt-4-format","id":"virtio-disk0","bootindex":1}' \ +-blockdev '{"driver":"nbd","server":{"type":"unix","path":"/tmp/lib/domain--1-QEMUGuest1/nbdkit-libvirt-3-storage.socket"},"node-name":"libvirt-3-storage","auto-read-only":true,"discard":"unmap"}' \ +-blockdev '{"node-name":"libvirt-3-format","read-only":false,"driver":"raw","file":"libvirt-3-storage"}' \ +-device '{"driver":"virtio-blk-pci","bus":"pci.0","addr":"0x3","drive":"libvirt-3-format","id":"virtio-disk1"}' \ +-blockdev '{"driver":"nbd","server":{"type":"unix","path":"/tmp/lib/domain--1-QEMUGuest1/nbdkit-libvirt-2-storage.socket"},"node-name":"libvirt-2-storage","auto-read-only":true,"discard":"unmap"}' \ +-blockdev '{"node-name":"libvirt-2-format","read-only":false,"driver":"raw","file":"libvirt-2-storage"}' \ +-device '{"driver":"virtio-blk-pci","bus":"pci.0","addr":"0x4","drive":"libvirt-2-format","id":"virtio-disk2"}' \ +-blockdev '{"driver":"nbd","server":{"type":"unix","path":"/tmp/lib/domain--1-QEMUGuest1/nbdkit-libvirt-1-storage.socket"},"node-name":"libvirt-1-storage","auto-read-only":true,"discard":"unmap"}' \ +-blockdev '{"node-name":"libvirt-1-format","read-only":false,"driver":"raw","file":"libvirt-1-storage"}' \ +-device '{"driver":"virtio-blk-pci","bus":"pci.0","addr":"0x5","drive":"libvirt-1-format","id":"virtio-disk3"}' \ +-audiodev '{"id":"audio1","driver":"none"}' \ +-sandbox on,obsolete=deny,elevateprivileges=deny,spawn=deny,resourcecontrol=deny \ +-msg timestamp=on diff --git a/tests/qemuxml2argvdata/disk-network-http-nbdkit.xml b/tests/qemuxml2argvdata/disk-network-http-nbdkit.xml new file mode 120000 index 0000000000..6a05204e8a --- /dev/null +++ b/tests/qemuxml2argvdata/disk-network-http-nbdkit.xml @@ -0,0 +1 @@ +disk-network-http.xml \ No newline at end of file diff --git a/tests/qemuxml2argvdata/disk-network-source-curl-nbdkit-backing.x86_64-latest.args b/tests/qemuxml2argvdata/disk-network-source-curl-nbdkit-backing.x86_64-latest.args new file mode 100644 index 0000000000..98cfcd219a --- /dev/null +++ b/tests/qemuxml2argvdata/disk-network-source-curl-nbdkit-backing.x86_64-latest.args @@ -0,0 +1,38 @@ +LC_ALL=C \ +PATH=/bin \ +HOME=/tmp/lib/domain--1-QEMUGuest1 \ +USER=test \ +LOGNAME=test \ +XDG_DATA_HOME=/tmp/lib/domain--1-QEMUGuest1/.local/share \ +XDG_CACHE_HOME=/tmp/lib/domain--1-QEMUGuest1/.cache \ +XDG_CONFIG_HOME=/tmp/lib/domain--1-QEMUGuest1/.config \ +/usr/bin/qemu-system-x86_64 \ +-name guest=QEMUGuest1,debug-threads=on \ +-S \ +-object '{"qom-type":"secret","id":"masterKey0","format":"raw","file":"/tmp/lib/domain--1-QEMUGuest1/master-key.aes"}' \ +-machine pc,usb=off,dump-guest-core=off,memory-backend=pc.ram \ +-accel tcg \ +-cpu qemu64 \ +-m 214 \ +-object '{"qom-type":"memory-backend-ram","id":"pc.ram","size":224395264}' \ +-overcommit mem-lock=off \ +-smp 1,sockets=1,cores=1,threads=1 \ +-uuid c7a5fdbd-edaf-9455-926a-d65c16db1809 \ +-display none \ +-no-user-config \ +-nodefaults \ +-chardev socket,id=charmonitor,fd=1729,server=on,wait=off \ +-mon chardev=charmonitor,id=monitor,mode=control \ +-rtc base=utc \ +-no-shutdown \ +-no-acpi \ +-boot strict=on \ +-device '{"driver":"piix3-usb-uhci","id":"usb","bus":"pci.0","addr":"0x1.0x2"}' \ +-blockdev '{"driver":"nbd","server":{"type":"unix","path":"/tmp/lib/domain--1-QEMUGuest1/nbdkit-libvirt-2-storage.socket"},"node-name":"libvirt-2-storage","auto-read-only":true,"discard":"unmap"}' \ +-blockdev '{"node-name":"libvirt-2-format","read-only":true,"driver":"qcow2","file":"libvirt-2-storage"}' \ +-blockdev '{"driver":"nbd","server":{"type":"unix","path":"/tmp/lib/domain--1-QEMUGuest1/nbdkit-libvirt-1-storage.socket"},"node-name":"libvirt-1-storage","auto-read-only":true,"discard":"unmap"}' \ +-blockdev '{"node-name":"libvirt-1-format","read-only":true,"driver":"qcow2","file":"libvirt-1-storage","backing":"libvirt-2-format"}' \ +-device '{"driver":"virtio-blk-pci","bus":"pci.0","addr":"0x2","drive":"libvirt-1-format","id":"virtio-disk0","bootindex":1}' \ +-audiodev '{"id":"audio1","driver":"none"}' \ +-sandbox on,obsolete=deny,elevateprivileges=deny,spawn=deny,resourcecontrol=deny \ +-msg timestamp=on diff --git a/tests/qemuxml2argvdata/disk-network-source-curl-nbdkit-backing.xml b/tests/qemuxml2argvdata/disk-network-source-curl-nbdkit-backing.xml new file mode 100644 index 0000000000..37a30fcbd6 --- /dev/null +++ b/tests/qemuxml2argvdata/disk-network-source-curl-nbdkit-backing.xml @@ -0,0 +1,45 @@ +<domain type='qemu'> + <name>QEMUGuest1</name> + <uuid>c7a5fdbd-edaf-9455-926a-d65c16db1809</uuid> + <memory unit='KiB'>219136</memory> + <currentMemory unit='KiB'>219136</currentMemory> + <vcpu placement='static'>1</vcpu> + <os> + <type arch='x86_64' machine='pc'>hvm</type> + <boot dev='hd'/> + </os> + <clock offset='utc'/> + <on_poweroff>destroy</on_poweroff> + <on_reboot>restart</on_reboot> + <on_crash>destroy</on_crash> + <devices> + <emulator>/usr/bin/qemu-system-x86_64</emulator> + <disk type='network' device='disk'> + <driver name='qemu' type='qcow2'/> + <source protocol='https' name='path/to/disk1.qcow2'> + <host name='https.example.org' port='8443'/> + <cookies> + <cookie name='cookie1'>cookievalue1</cookie> + <cookie name='cookie2'>cookievalue2</cookie> + </cookies> + </source> + <backingStore type='network'> + <format type='qcow2'/> + <source protocol='https' name='path/to/backing.qcow2'> + <host name='https.example2.org' port='8444'/> + <cookies> + <cookie name='cookie3'>cookievalue3</cookie> + <cookie name='cookie4'>cookievalue4</cookie> + </cookies> + </source> + </backingStore> + <target dev='vda' bus='virtio'/> + <readonly/> + </disk> + <controller type='usb' index='0'/> + <controller type='pci' index='0' model='pci-root'/> + <input type='mouse' bus='ps2'/> + <input type='keyboard' bus='ps2'/> + <memballoon model='none'/> + </devices> +</domain> diff --git a/tests/qemuxml2argvdata/disk-network-source-curl-nbdkit.x86_64-latest.args b/tests/qemuxml2argvdata/disk-network-source-curl-nbdkit.x86_64-latest.args new file mode 100644 index 0000000000..ec193bb10a --- /dev/null +++ b/tests/qemuxml2argvdata/disk-network-source-curl-nbdkit.x86_64-latest.args @@ -0,0 +1,50 @@ +LC_ALL=C \ +PATH=/bin \ +HOME=/tmp/lib/domain--1-QEMUGuest1 \ +USER=test \ +LOGNAME=test \ +XDG_DATA_HOME=/tmp/lib/domain--1-QEMUGuest1/.local/share \ +XDG_CACHE_HOME=/tmp/lib/domain--1-QEMUGuest1/.cache \ +XDG_CONFIG_HOME=/tmp/lib/domain--1-QEMUGuest1/.config \ +/usr/bin/qemu-system-x86_64 \ +-name guest=QEMUGuest1,debug-threads=on \ +-S \ +-object '{"qom-type":"secret","id":"masterKey0","format":"raw","file":"/tmp/lib/domain--1-QEMUGuest1/master-key.aes"}' \ +-machine pc,usb=off,dump-guest-core=off,memory-backend=pc.ram \ +-accel tcg \ +-cpu qemu64 \ +-m 214 \ +-object '{"qom-type":"memory-backend-ram","id":"pc.ram","size":224395264}' \ +-overcommit mem-lock=off \ +-smp 1,sockets=1,cores=1,threads=1 \ +-uuid c7a5fdbd-edaf-9455-926a-d65c16db1809 \ +-display none \ +-no-user-config \ +-nodefaults \ +-chardev socket,id=charmonitor,fd=1729,server=on,wait=off \ +-mon chardev=charmonitor,id=monitor,mode=control \ +-rtc base=utc \ +-no-shutdown \ +-no-acpi \ +-boot strict=on \ +-device '{"driver":"piix3-usb-uhci","id":"usb","bus":"pci.0","addr":"0x1.0x2"}' \ +-device '{"driver":"ahci","id":"sata0","bus":"pci.0","addr":"0x2"}' \ +-blockdev '{"driver":"nbd","server":{"type":"unix","path":"/tmp/lib/domain--1-QEMUGuest1/nbdkit-libvirt-5-storage.socket"},"node-name":"libvirt-5-storage","auto-read-only":true,"discard":"unmap"}' \ +-blockdev '{"node-name":"libvirt-5-format","read-only":true,"driver":"raw","file":"libvirt-5-storage"}' \ +-device '{"driver":"virtio-blk-pci","bus":"pci.0","addr":"0x3","drive":"libvirt-5-format","id":"virtio-disk0","bootindex":1}' \ +-object '{"qom-type":"secret","id":"libvirt-4-format-encryption-secret0","data":"9eao5F8qtkGt+seB1HYivWIxbtwUu6MQtg1zpj/oDtUsPr1q8wBYM91uEHCn6j/1","keyid":"masterKey0","iv":"AAECAwQFBgcICQoLDA0ODw==","format":"base64"}' \ +-blockdev '{"driver":"nbd","server":{"type":"unix","path":"/tmp/lib/domain--1-QEMUGuest1/nbdkit-libvirt-4-storage.socket"},"node-name":"libvirt-4-storage","auto-read-only":true,"discard":"unmap"}' \ +-blockdev '{"node-name":"libvirt-4-format","read-only":false,"driver":"luks","key-secret":"libvirt-4-format-encryption-secret0","file":"libvirt-4-storage"}' \ +-device '{"driver":"virtio-blk-pci","bus":"pci.0","addr":"0x4","drive":"libvirt-4-format","id":"virtio-disk4"}' \ +-blockdev '{"driver":"nbd","server":{"type":"unix","path":"/tmp/lib/domain--1-QEMUGuest1/nbdkit-libvirt-3-storage.socket"},"node-name":"libvirt-3-storage","auto-read-only":true,"discard":"unmap"}' \ +-blockdev '{"node-name":"libvirt-3-format","read-only":true,"driver":"raw","file":"libvirt-3-storage"}' \ +-device '{"driver":"ide-cd","bus":"sata0.1","drive":"libvirt-3-format","id":"sata0-0-1"}' \ +-blockdev '{"driver":"nbd","server":{"type":"unix","path":"/tmp/lib/domain--1-QEMUGuest1/nbdkit-libvirt-2-storage.socket"},"node-name":"libvirt-2-storage","auto-read-only":true,"discard":"unmap"}' \ +-blockdev '{"node-name":"libvirt-2-format","read-only":true,"driver":"raw","file":"libvirt-2-storage"}' \ +-device '{"driver":"ide-cd","bus":"sata0.2","drive":"libvirt-2-format","id":"sata0-0-2"}' \ +-blockdev '{"driver":"nbd","server":{"type":"unix","path":"/tmp/lib/domain--1-QEMUGuest1/nbdkit-libvirt-1-storage.socket"},"node-name":"libvirt-1-storage","auto-read-only":true,"discard":"unmap"}' \ +-blockdev '{"node-name":"libvirt-1-format","read-only":true,"driver":"raw","file":"libvirt-1-storage"}' \ +-device '{"driver":"ide-cd","bus":"sata0.3","drive":"libvirt-1-format","id":"sata0-0-3"}' \ +-audiodev '{"id":"audio1","driver":"none"}' \ +-sandbox on,obsolete=deny,elevateprivileges=deny,spawn=deny,resourcecontrol=deny \ +-msg timestamp=on diff --git a/tests/qemuxml2argvdata/disk-network-source-curl-nbdkit.xml b/tests/qemuxml2argvdata/disk-network-source-curl-nbdkit.xml new file mode 120000 index 0000000000..4a1e40bd70 --- /dev/null +++ b/tests/qemuxml2argvdata/disk-network-source-curl-nbdkit.xml @@ -0,0 +1 @@ +disk-network-source-curl.xml \ No newline at end of file diff --git a/tests/qemuxml2argvdata/disk-network-source-curl.x86_64-latest.args b/tests/qemuxml2argvdata/disk-network-source-curl.x86_64-latest.args new file mode 100644 index 0000000000..ec6dd13f6c --- /dev/null +++ b/tests/qemuxml2argvdata/disk-network-source-curl.x86_64-latest.args @@ -0,0 +1,53 @@ +LC_ALL=C \ +PATH=/bin \ +HOME=/tmp/lib/domain--1-QEMUGuest1 \ +USER=test \ +LOGNAME=test \ +XDG_DATA_HOME=/tmp/lib/domain--1-QEMUGuest1/.local/share \ +XDG_CACHE_HOME=/tmp/lib/domain--1-QEMUGuest1/.cache \ +XDG_CONFIG_HOME=/tmp/lib/domain--1-QEMUGuest1/.config \ +/usr/bin/qemu-system-x86_64 \ +-name guest=QEMUGuest1,debug-threads=on \ +-S \ +-object '{"qom-type":"secret","id":"masterKey0","format":"raw","file":"/tmp/lib/domain--1-QEMUGuest1/master-key.aes"}' \ +-machine pc,usb=off,dump-guest-core=off,memory-backend=pc.ram \ +-accel tcg \ +-cpu qemu64 \ +-m 214 \ +-object '{"qom-type":"memory-backend-ram","id":"pc.ram","size":224395264}' \ +-overcommit mem-lock=off \ +-smp 1,sockets=1,cores=1,threads=1 \ +-uuid c7a5fdbd-edaf-9455-926a-d65c16db1809 \ +-display none \ +-no-user-config \ +-nodefaults \ +-chardev socket,id=charmonitor,fd=1729,server=on,wait=off \ +-mon chardev=charmonitor,id=monitor,mode=control \ +-rtc base=utc \ +-no-shutdown \ +-no-acpi \ +-boot strict=on \ +-device '{"driver":"piix3-usb-uhci","id":"usb","bus":"pci.0","addr":"0x1.0x2"}' \ +-device '{"driver":"ahci","id":"sata0","bus":"pci.0","addr":"0x2"}' \ +-object '{"qom-type":"secret","id":"libvirt-5-storage-httpcookie-secret0","data":"BUU0KmnWfonHdjzhYhwVQZ5iTI1KweTJ22q8XWUVoBCVu1z70reDuczPBIabZtC3","keyid":"masterKey0","iv":"AAECAwQFBgcICQoLDA0ODw==","format":"base64"}' \ +-blockdev '{"driver":"https","url":"https://https.example.org:8443/path/to/disk1.iso","cookie-secret":"libvirt-5-storage-httpcookie-secret0","node-name":"libvirt-5-storage","auto-read-only":true,"discard":"unmap"}' \ +-blockdev '{"node-name":"libvirt-5-format","read-only":true,"driver":"raw","file":"libvirt-5-storage"}' \ +-device '{"driver":"virtio-blk-pci","bus":"pci.0","addr":"0x3","drive":"libvirt-5-format","id":"virtio-disk0","bootindex":1}' \ +-object '{"qom-type":"secret","id":"libvirt-4-format-encryption-secret0","data":"9eao5F8qtkGt+seB1HYivWIxbtwUu6MQtg1zpj/oDtUsPr1q8wBYM91uEHCn6j/1","keyid":"masterKey0","iv":"AAECAwQFBgcICQoLDA0ODw==","format":"base64"}' \ +-object '{"qom-type":"secret","id":"libvirt-4-storage-httpcookie-secret0","data":"BUU0KmnWfonHdjzhYhwVQZ5iTI1KweTJ22q8XWUVoBCVu1z70reDuczPBIabZtC3","keyid":"masterKey0","iv":"AAECAwQFBgcICQoLDA0ODw==","format":"base64"}' \ +-blockdev '{"driver":"https","url":"https://https.example.org:8443/path/to/disk5.iso?foo=bar","sslverify":false,"cookie-secret":"libvirt-4-storage-httpcookie-secret0","node-name":"libvirt-4-storage","auto-read-only":true,"discard":"unmap"}' \ +-blockdev '{"node-name":"libvirt-4-format","read-only":false,"driver":"luks","key-secret":"libvirt-4-format-encryption-secret0","file":"libvirt-4-storage"}' \ +-device '{"driver":"virtio-blk-pci","bus":"pci.0","addr":"0x4","drive":"libvirt-4-format","id":"virtio-disk4"}' \ +-object '{"qom-type":"secret","id":"libvirt-3-storage-httpcookie-secret0","data":"BUU0KmnWfonHdjzhYhwVQZ5iTI1KweTJ22q8XWUVoBBv7TuTgTkyAyOPpC2P5qLbOIypLoHpppjz+u5O+X8oT+jA1m7q/OJQ8dk2EFD5c0A=","keyid":"masterKey0","iv":"AAECAwQFBgcICQoLDA0ODw==","format":"base64"}' \ +-blockdev '{"driver":"http","url":"http://http.example.org:8080/path/to/disk2.iso","cookie-secret":"libvirt-3-storage-httpcookie-secret0","node-name":"libvirt-3-storage","auto-read-only":true,"discard":"unmap"}' \ +-blockdev '{"node-name":"libvirt-3-format","read-only":true,"driver":"raw","file":"libvirt-3-storage"}' \ +-device '{"driver":"ide-cd","bus":"sata0.1","drive":"libvirt-3-format","id":"sata0-0-1"}' \ +-blockdev '{"driver":"ftp","url":"ftp://ftp.example.org:20/path/to/disk3.iso","node-name":"libvirt-2-storage","auto-read-only":true,"discard":"unmap"}' \ +-blockdev '{"node-name":"libvirt-2-format","read-only":true,"driver":"raw","file":"libvirt-2-storage"}' \ +-device '{"driver":"ide-cd","bus":"sata0.2","drive":"libvirt-2-format","id":"sata0-0-2"}' \ +-blockdev '{"driver":"ftps","url":"ftps://ftps.example.org:22/path/to/disk4.iso","node-name":"libvirt-1-storage","auto-read-only":true,"discard":"unmap"}' \ +-blockdev '{"node-name":"libvirt-1-format","read-only":true,"driver":"raw","file":"libvirt-1-storage"}' \ +-device '{"driver":"ide-cd","bus":"sata0.3","drive":"libvirt-1-format","id":"sata0-0-3"}' \ +-audiodev '{"id":"audio1","driver":"none"}' \ +-sandbox on,obsolete=deny,elevateprivileges=deny,spawn=deny,resourcecontrol=deny \ +-msg timestamp=on diff --git a/tests/qemuxml2argvdata/disk-network-source-curl.xml b/tests/qemuxml2argvdata/disk-network-source-curl.xml new file mode 100644 index 0000000000..1e50314abe --- /dev/null +++ b/tests/qemuxml2argvdata/disk-network-source-curl.xml @@ -0,0 +1,71 @@ +<domain type='qemu'> + <name>QEMUGuest1</name> + <uuid>c7a5fdbd-edaf-9455-926a-d65c16db1809</uuid> + <memory unit='KiB'>219136</memory> + <currentMemory unit='KiB'>219136</currentMemory> + <vcpu placement='static'>1</vcpu> + <os> + <type arch='x86_64' machine='pc'>hvm</type> + <boot dev='hd'/> + </os> + <clock offset='utc'/> + <on_poweroff>destroy</on_poweroff> + <on_reboot>restart</on_reboot> + <on_crash>destroy</on_crash> + <devices> + <emulator>/usr/bin/qemu-system-x86_64</emulator> + <disk type='network' device='disk'> + <source protocol='https' name='path/to/disk1.iso'> + <host name='https.example.org' port='8443'/> + <cookies> + <cookie name='cookie1'>cookievalue1</cookie> + <cookie name='cookie2'>cookievalue2</cookie> + </cookies> + </source> + <target dev='vda' bus='virtio'/> + <readonly/> + </disk> + <disk type='network' device='cdrom'> + <source protocol='http' name='path/to/disk2.iso'> + <host name='http.example.org' port='8080'/> + <cookies> + <cookie name='cookie1'>cookievalue1</cookie> + <cookie name='cookie2'>cookievalue2</cookie> + <cookie name='cookie3'>cookievalue3</cookie> + </cookies> + </source> + <target dev='hdb' bus='sata'/> + </disk> + <disk type='network' device='cdrom'> + <source protocol='ftp' name='path/to/disk3.iso'> + <host name='ftp.example.org' port='20'/> + </source> + <target dev='hdc' bus='sata'/> + </disk> + <disk type='network' device='cdrom'> + <source protocol='ftps' name='path/to/disk4.iso'> + <host name='ftps.example.org' port='22'/> + </source> + <target dev='hdd' bus='sata'/> + </disk> + <disk type='network' device='disk'> + <source protocol='https' name='path/to/disk5.iso' query='foo=bar'> + <host name='https.example.org' port='8443'/> + <ssl verify='no'/> + <cookies> + <cookie name='cookie1'>cookievalue1</cookie> + <cookie name='cookie2'>cookievalue2</cookie> + </cookies> + <encryption format='luks'> + <secret type='passphrase' uuid='1148b693-0843-4cef-9f97-8feb4e1ae365'/> + </encryption> + </source> + <target dev='vde' bus='virtio'/> + </disk> + <controller type='usb' index='0'/> + <controller type='pci' index='0' model='pci-root'/> + <input type='mouse' bus='ps2'/> + <input type='keyboard' bus='ps2'/> + <memballoon model='none'/> + </devices> +</domain> diff --git a/tests/qemuxml2argvdata/disk-network-ssh-nbdkit.x86_64-latest.args b/tests/qemuxml2argvdata/disk-network-ssh-nbdkit.x86_64-latest.args new file mode 100644 index 0000000000..e22ba095b1 --- /dev/null +++ b/tests/qemuxml2argvdata/disk-network-ssh-nbdkit.x86_64-latest.args @@ -0,0 +1,36 @@ +LC_ALL=C \ +PATH=/bin \ +HOME=/tmp/lib/domain--1-QEMUGuest1 \ +USER=test \ +LOGNAME=test \ +XDG_DATA_HOME=/tmp/lib/domain--1-QEMUGuest1/.local/share \ +XDG_CACHE_HOME=/tmp/lib/domain--1-QEMUGuest1/.cache \ +XDG_CONFIG_HOME=/tmp/lib/domain--1-QEMUGuest1/.config \ +/usr/bin/qemu-system-x86_64 \ +-name guest=QEMUGuest1,debug-threads=on \ +-S \ +-object '{"qom-type":"secret","id":"masterKey0","format":"raw","file":"/tmp/lib/domain--1-QEMUGuest1/master-key.aes"}' \ +-machine pc,usb=off,dump-guest-core=off,memory-backend=pc.ram \ +-accel kvm \ +-cpu qemu64 \ +-m 214 \ +-object '{"qom-type":"memory-backend-ram","id":"pc.ram","size":224395264}' \ +-overcommit mem-lock=off \ +-smp 1,sockets=1,cores=1,threads=1 \ +-uuid c7a5fdbd-edaf-9455-926a-d65c16db1809 \ +-display none \ +-no-user-config \ +-nodefaults \ +-chardev socket,id=charmonitor,fd=1729,server=on,wait=off \ +-mon chardev=charmonitor,id=monitor,mode=control \ +-rtc base=utc \ +-no-shutdown \ +-no-acpi \ +-boot strict=on \ +-device '{"driver":"piix3-usb-uhci","id":"usb","bus":"pci.0","addr":"0x1.0x2"}' \ +-blockdev '{"driver":"nbd","server":{"type":"unix","path":"/tmp/lib/domain--1-QEMUGuest1/nbdkit-libvirt-1-storage.socket"},"node-name":"libvirt-1-storage","auto-read-only":true,"discard":"unmap"}' \ +-blockdev '{"node-name":"libvirt-1-format","read-only":false,"driver":"raw","file":"libvirt-1-storage"}' \ +-device '{"driver":"virtio-blk-pci","bus":"pci.0","addr":"0x2","drive":"libvirt-1-format","id":"virtio-disk0","bootindex":1}' \ +-audiodev '{"id":"audio1","driver":"none"}' \ +-sandbox on,obsolete=deny,elevateprivileges=deny,spawn=deny,resourcecontrol=deny \ +-msg timestamp=on diff --git a/tests/qemuxml2argvdata/disk-network-ssh-nbdkit.xml b/tests/qemuxml2argvdata/disk-network-ssh-nbdkit.xml new file mode 120000 index 0000000000..b0589bdfb5 --- /dev/null +++ b/tests/qemuxml2argvdata/disk-network-ssh-nbdkit.xml @@ -0,0 +1 @@ +disk-network-ssh.xml \ No newline at end of file diff --git a/tests/qemuxml2argvtest.c b/tests/qemuxml2argvtest.c index 0032bafdce..60ec42428c 100644 --- a/tests/qemuxml2argvtest.c +++ b/tests/qemuxml2argvtest.c @@ -1286,6 +1286,7 @@ mymain(void) DO_TEST_CAPS_LATEST("disk-cdrom-empty-network-invalid"); DO_TEST_CAPS_LATEST("disk-cdrom-bus-other"); DO_TEST_CAPS_LATEST("disk-cdrom-network"); + DO_TEST_CAPS_LATEST_NBDKIT("disk-cdrom-network-nbdkit", QEMU_NBDKIT_CAPS_PLUGIN_CURL); DO_TEST_CAPS_LATEST("disk-cdrom-tray"); DO_TEST_CAPS_LATEST("disk-floppy"); DO_TEST_CAPS_LATEST("disk-floppy-q35"); @@ -1326,6 +1327,9 @@ mymain(void) /* qemu-6.0 is the last qemu version supporting sheepdog */ DO_TEST_CAPS_VER("disk-network-sheepdog", "6.0.0"); DO_TEST_CAPS_LATEST("disk-network-source-auth"); + DO_TEST_CAPS_LATEST("disk-network-source-curl"); + DO_TEST_CAPS_LATEST_NBDKIT("disk-network-source-curl-nbdkit", QEMU_NBDKIT_CAPS_PLUGIN_CURL); + DO_TEST_CAPS_LATEST_NBDKIT("disk-network-source-curl-nbdkit-backing", QEMU_NBDKIT_CAPS_PLUGIN_CURL); DO_TEST_CAPS_LATEST("disk-network-nfs"); driver.config->vxhsTLS = 1; driver.config->nbdTLSx509secretUUID = g_strdup("6fd3f62d-9fe7-4a4e-a869-7acd6376d8ea"); @@ -1336,7 +1340,9 @@ mymain(void) DO_TEST_CAPS_LATEST("disk-network-tlsx509-nbd-hostname"); DO_TEST_CAPS_VER("disk-network-tlsx509-vxhs", "5.0.0"); DO_TEST_CAPS_LATEST("disk-network-http"); + DO_TEST_CAPS_LATEST_NBDKIT("disk-network-http-nbdkit", QEMU_NBDKIT_CAPS_PLUGIN_CURL); DO_TEST_CAPS_LATEST("disk-network-ssh"); + DO_TEST_CAPS_LATEST_NBDKIT("disk-network-ssh-nbdkit", QEMU_NBDKIT_CAPS_PLUGIN_SSH); driver.config->vxhsTLS = 0; VIR_FREE(driver.config->vxhsTLSx509certdir); DO_TEST_CAPS_LATEST("disk-no-boot"); -- 2.37.3

On Thu, Oct 20, 2022 at 16:59:04 -0500, Jonathon Jongsma wrote:
For virStorageSource objects that contain an nbdkitProcess, start that nbdkit process to serve that network drive and then pass the nbdkit socket to qemu rather than sending the network url to qemu directly.
Signed-off-by: Jonathon Jongsma <jjongsma@redhat.com> --- src/qemu/qemu_block.c | 162 +++++++++++------- src/qemu/qemu_domain.c | 21 ++- src/qemu/qemu_extdevice.c | 48 ++++++ src/qemu/qemu_nbdkit.c | 63 +++++++ src/qemu/qemu_nbdkit.h | 13 ++ ...sk-cdrom-network-nbdkit.x86_64-latest.args | 42 +++++ .../disk-cdrom-network-nbdkit.xml | 1 + ...isk-network-http-nbdkit.x86_64-latest.args | 45 +++++ .../disk-network-http-nbdkit.xml | 1 + ...rce-curl-nbdkit-backing.x86_64-latest.args | 38 ++++ ...isk-network-source-curl-nbdkit-backing.xml | 45 +++++ ...work-source-curl-nbdkit.x86_64-latest.args | 50 ++++++ .../disk-network-source-curl-nbdkit.xml | 1 + ...isk-network-source-curl.x86_64-latest.args | 53 ++++++ .../disk-network-source-curl.xml | 71 ++++++++ ...disk-network-ssh-nbdkit.x86_64-latest.args | 36 ++++ .../disk-network-ssh-nbdkit.xml | 1 + tests/qemuxml2argvtest.c | 6 + 18 files changed, 627 insertions(+), 70 deletions(-) create mode 100644 tests/qemuxml2argvdata/disk-cdrom-network-nbdkit.x86_64-latest.args create mode 120000 tests/qemuxml2argvdata/disk-cdrom-network-nbdkit.xml create mode 100644 tests/qemuxml2argvdata/disk-network-http-nbdkit.x86_64-latest.args create mode 120000 tests/qemuxml2argvdata/disk-network-http-nbdkit.xml create mode 100644 tests/qemuxml2argvdata/disk-network-source-curl-nbdkit-backing.x86_64-latest.args create mode 100644 tests/qemuxml2argvdata/disk-network-source-curl-nbdkit-backing.xml create mode 100644 tests/qemuxml2argvdata/disk-network-source-curl-nbdkit.x86_64-latest.args create mode 120000 tests/qemuxml2argvdata/disk-network-source-curl-nbdkit.xml create mode 100644 tests/qemuxml2argvdata/disk-network-source-curl.x86_64-latest.args create mode 100644 tests/qemuxml2argvdata/disk-network-source-curl.xml create mode 100644 tests/qemuxml2argvdata/disk-network-ssh-nbdkit.x86_64-latest.args create mode 120000 tests/qemuxml2argvdata/disk-network-ssh-nbdkit.xml
Note that since this commit is most likely misplaced since it enables nbdkit use before the implementation of passing secrets is done, thus at this point the feature will be knowingly broken. You'll need to move it further back after adding the passing of secrets.
diff --git a/src/qemu/qemu_block.c b/src/qemu/qemu_block.c index b82e3311e1..224d468799 100644 --- a/src/qemu/qemu_block.c +++ b/src/qemu/qemu_block.c @@ -439,6 +439,32 @@ qemuBlockStorageSourceGetCURLProps(virStorageSource *src, }
+static virJSONValue * +qemuBlockStorageSourceGetNbdkitProps(virStorageSource *src) +{ + qemuDomainStorageSourcePrivate *srcPriv = QEMU_DOMAIN_STORAGE_SOURCE_PRIVATE(src); + virJSONValue *ret = NULL; + g_autoptr(virJSONValue) serverprops = NULL; + virStorageNetHostDef host = { .transport = VIR_STORAGE_NET_HOST_TRANS_UNIX }; + + /* srcPriv->nbdkitProcess will already be initialized if we can use nbdkit + * to proxy this storage source */ + if (!(srcPriv && srcPriv->nbdkitProcess)) + return NULL; + + host.socket = srcPriv->nbdkitProcess->socketfile; + serverprops = qemuBlockStorageSourceBuildJSONSocketAddress(&host); + + if (!serverprops) + return NULL; + + if (virJSONValueObjectAdd(&ret, "a:server", &serverprops, NULL) < 0) + return NULL; + + return ret; +} + + static virJSONValue * qemuBlockStorageSourceGetISCSIProps(virStorageSource *src, bool onlytarget) @@ -851,69 +877,75 @@ qemuBlockStorageSourceGetBackendProps(virStorageSource *src,
I'll try decomposing this function a bit so that special cases such as this one can be added easily. In the future we'll need a special case for QSD too. I'm also starting to think that we'll need a per-storage source configuration of the used backend, but I'll have to consider to what granularity we might want to go.
return NULL;
case VIR_STORAGE_TYPE_NETWORK:
[...]
@@ -2196,6 +2228,7 @@ qemuBlockStorageSourceCreateGetStorageProps(virStorageSource *src, g_autoptr(virJSONValue) location = NULL; const char *driver = NULL; const char *filename = NULL; + qemuDomainStorageSourcePrivate *srcPriv = QEMU_DOMAIN_STORAGE_SOURCE_PRIVATE(src);
switch (actualType) { case VIR_STORAGE_TYPE_FILE: @@ -2224,6 +2257,13 @@ qemuBlockStorageSourceCreateGetStorageProps(virStorageSource *src, break;
case VIR_STORAGE_NET_PROTOCOL_SSH: + if (srcPriv->nbdkitProcess) { + /* disk creation not yet supported with nbdkit, and even if it + * was supported, it would not be done with blockdev-create + * props */ + return 0;
Normally we wouldn't be able to do this, but given how extremely clumsy and not really documented it is to set up SSH disks I think we might be okay breaking this unused functionality. Although at this point you should probably propose a separate patch to disallow this even without nbdkit to prevent any form of regressions in case ssh gets to become useful.
+ } + driver = "ssh"; if (!(location = qemuBlockStorageSourceGetSshProps(src))) return -1; diff --git a/src/qemu/qemu_domain.c b/src/qemu/qemu_domain.c index 2ae87392cb..a3fafdd9e3 100644 --- a/src/qemu/qemu_domain.c +++ b/src/qemu/qemu_domain.c @@ -10918,21 +10918,24 @@ qemuDomainPrepareStorageSourceBlockdevNodename(virDomainDiskDef *disk, if (qemuDomainSecretStorageSourcePrepareEncryption(priv, src, src->nodeformat) < 0) return -1; - if (qemuDomainSecretStorageSourcePrepareAuth(priv, src, - src->nodestorage) < 0) - return -1;
if (qemuDomainPrepareStorageSourcePR(src, priv, src->nodestorage) < 0) return -1;
- if (qemuDomainPrepareStorageSourceTLS(src, cfg, src->nodestorage, - priv) < 0) - return -1; + if (!qemuDomainPrepareStorageSourceNbdkit(src, cfg, src->nodestorage, priv)) { + /* If we're using nbdkit to serve the storage source, we don't pass + * authentication secrets to qemu, but will pass them to nbdkit instead */ + if (qemuDomainSecretStorageSourcePrepareAuth(priv, src, + src->nodestorage) < 0) + return -1;
- if (qemuDomainPrepareStorageSourceNFS(src) < 0) - return -1; + if (qemuDomainPrepareStorageSourceTLS(src, cfg, src->nodestorage, + priv) < 0) + return -1;
- qemuDomainPrepareStorageSourceNbdkit(src, cfg, src->nodestorage, priv); + if (qemuDomainPrepareStorageSourceNFS(src) < 0) + return -1;
I'm not sure which pattern you've picked here. The persistent reservations helper is also tied to the protocol layer node, but is not included in the condition. On the other hand the setup of the NFS protocol backend is inlcuded here although it's not handled by nbdkit.
+ }
return 0; } diff --git a/src/qemu/qemu_extdevice.c b/src/qemu/qemu_extdevice.c index 24a57b0f74..4a3d082f1e 100644 --- a/src/qemu/qemu_extdevice.c +++ b/src/qemu/qemu_extdevice.c @@ -219,6 +219,17 @@ qemuExtDevicesStart(virQEMUDriver *driver, return -1; }
+ for (i = 0; i < def->ndisks; i++) { + virDomainDiskDef *disk = def->disks[i]; + if (qemuNbdkitStartStorageSource(driver, vm, disk->src) < 0) + return -1; + } + + if (def->os.loader && def->os.loader->nvram) { + if (qemuNbdkitStartStorageSource(driver, vm, def->os.loader->nvram) < 0) + return -1; + } + return 0; }
@@ -263,6 +274,14 @@ qemuExtDevicesStop(virQEMUDriver *driver, fs->fsdriver == VIR_DOMAIN_FS_DRIVER_TYPE_VIRTIOFS) qemuVirtioFSStop(driver, vm, fs); } + + for (i = 0; i < def->ndisks; i++) { + virDomainDiskDef *disk = def->disks[i]; + qemuNbdkitStopStorageSource(disk->src); + } + + if (def->os.loader && def->os.loader->nvram) + qemuNbdkitStopStorageSource(def->os.loader->nvram); }
@@ -292,6 +311,24 @@ qemuExtDevicesHasDevice(virDomainDef *def)
You also need to add appropriate code into qemuExtDevicesHasDevice, as othewise qemuSetupCgroupForExtDevices() does nothing and all of the code you are adding below is pointless up to the point when something else requires and extdevice.
+/* recursively setup nbdkit cgroups for backing chain of src */ +static int qemuExtDevicesSetupCgroupNbdkit(virStorageSource *src, + virCgroup *cgroup) +{ + qemuDomainStorageSourcePrivate *priv = QEMU_DOMAIN_STORAGE_SOURCE_PRIVATE(src); + + if (src->backingStore) + if (qemuExtDevicesSetupCgroupNbdkit(src->backingStore, cgroup) < 0) + return -1; + + if (priv && priv->nbdkitProcess && + qemuNbdkitProcessSetupCgroup(priv->nbdkitProcess, cgroup) < 0) + return -1; + + return 0; +} + + int qemuExtDevicesSetupCgroup(virQEMUDriver *driver, virDomainObj *vm, @@ -325,6 +362,17 @@ qemuExtDevicesSetupCgroup(virQEMUDriver *driver, return -1; }
+ for (i = 0; i < def->ndisks; i++) { + virDomainDiskDef *disk = def->disks[i]; + if (qemuExtDevicesSetupCgroupNbdkit(disk->src, cgroup) < 0) + return -1; + } + + if (def->os.loader && def->os.loader->nvram) { + if (qemuExtDevicesSetupCgroupNbdkit(def->os.loader->nvram, cgroup) < 0) + return -1;
[...]
diff --git a/src/qemu/qemu_nbdkit.c b/src/qemu/qemu_nbdkit.c index 5b47a15112..8cb9e0e604 100644 --- a/src/qemu/qemu_nbdkit.c +++ b/src/qemu/qemu_nbdkit.c @@ -672,6 +672,61 @@ qemuNbdkitInitStorageSource(qemuNbdkitCaps *caps, }
+static int +qemuNbdkitStartStorageSourceOne(virQEMUDriver *driver, + virDomainObj *vm, + virStorageSource *src) +{ + qemuDomainStorageSourcePrivate *priv = QEMU_DOMAIN_STORAGE_SOURCE_PRIVATE(src); + + if (priv && priv->nbdkitProcess && + qemuNbdkitProcessStart(priv->nbdkitProcess, vm, driver) < 0) + return -1; + + return 0; +} + + +/* recursively start nbdkit for backing chain of src */ +int +qemuNbdkitStartStorageSource(virQEMUDriver *driver, + virDomainObj *vm, + virStorageSource *src) +{ + virStorageSource *backing; + + for (backing = src->backingStore; backing != NULL; backing = backing->backingStore) + if (qemuNbdkitStartStorageSourceOne(driver, vm, backing) < 0) + return -1; + + return qemuNbdkitStartStorageSourceOne(driver, vm, src); +} + + +static void +qemuNbdkitStopStorageSourceOne(virStorageSource *src) +{ + qemuDomainStorageSourcePrivate *priv = QEMU_DOMAIN_STORAGE_SOURCE_PRIVATE(src); + + if (priv && priv->nbdkitProcess && + qemuNbdkitProcessStop(priv->nbdkitProcess) < 0) + VIR_WARN("Unable to stop nbdkit for storage source '%s'", src->nodestorage); +}
[...] This patch is also missing appropriate additions to qemuDomainAttachDiskGeneric and qemuDomainRemoveDiskDevice to support hotplug and hotunplug of disks backed via nbdkit. [...]
diff --git a/tests/qemuxml2argvdata/disk-network-ssh-nbdkit.xml b/tests/qemuxml2argvdata/disk-network-ssh-nbdkit.xml new file mode 120000 index 0000000000..b0589bdfb5 --- /dev/null +++ b/tests/qemuxml2argvdata/disk-network-ssh-nbdkit.xml @@ -0,0 +1 @@ +disk-network-ssh.xml \ No newline at end of file diff --git a/tests/qemuxml2argvtest.c b/tests/qemuxml2argvtest.c index 0032bafdce..60ec42428c 100644 --- a/tests/qemuxml2argvtest.c +++ b/tests/qemuxml2argvtest.c @@ -1286,6 +1286,7 @@ mymain(void) DO_TEST_CAPS_LATEST("disk-cdrom-empty-network-invalid"); DO_TEST_CAPS_LATEST("disk-cdrom-bus-other"); DO_TEST_CAPS_LATEST("disk-cdrom-network"); + DO_TEST_CAPS_LATEST_NBDKIT("disk-cdrom-network-nbdkit", QEMU_NBDKIT_CAPS_PLUGIN_CURL); DO_TEST_CAPS_LATEST("disk-cdrom-tray"); DO_TEST_CAPS_LATEST("disk-floppy"); DO_TEST_CAPS_LATEST("disk-floppy-q35"); @@ -1326,6 +1327,9 @@ mymain(void) /* qemu-6.0 is the last qemu version supporting sheepdog */ DO_TEST_CAPS_VER("disk-network-sheepdog", "6.0.0"); DO_TEST_CAPS_LATEST("disk-network-source-auth"); + DO_TEST_CAPS_LATEST("disk-network-source-curl"); + DO_TEST_CAPS_LATEST_NBDKIT("disk-network-source-curl-nbdkit", QEMU_NBDKIT_CAPS_PLUGIN_CURL); + DO_TEST_CAPS_LATEST_NBDKIT("disk-network-source-curl-nbdkit-backing", QEMU_NBDKIT_CAPS_PLUGIN_CURL);
Note that you can have multiple disks to test multiple scenarios so there's no need to add special cases for those where backing files are used. Preferrably also create the new test files as symlinks of the originals so that we have equivalent thesting for both code paths.
DO_TEST_CAPS_LATEST("disk-network-nfs"); driver.config->vxhsTLS = 1; driver.config->nbdTLSx509secretUUID = g_strdup("6fd3f62d-9fe7-4a4e-a869-7acd6376d8ea");

We were testing the arguments that were being passed to qemu when a disk was being served by nbdkit, but the arguments used to start nbdkit itself were not testable. This adds a test to ensure that we're invoking nbdkit correctly for various disk source definitions. For now, expect failure for tests that use sensitive data such as passwords and cookies. Signed-off-by: Jonathon Jongsma <jjongsma@redhat.com> --- src/qemu/qemu_nbdkit.c | 4 +- src/qemu/qemu_nbdkitpriv.h | 31 +++ tests/meson.build | 1 + .../disk-network-ssh.args.disk0 | 7 + tests/qemunbdkittest.c | 239 ++++++++++++++++++ 5 files changed, 281 insertions(+), 1 deletion(-) create mode 100644 src/qemu/qemu_nbdkitpriv.h create mode 100644 tests/qemunbdkitdata/disk-network-ssh.args.disk0 create mode 100644 tests/qemunbdkittest.c diff --git a/src/qemu/qemu_nbdkit.c b/src/qemu/qemu_nbdkit.c index 8cb9e0e604..882a074211 100644 --- a/src/qemu/qemu_nbdkit.c +++ b/src/qemu/qemu_nbdkit.c @@ -34,6 +34,8 @@ #include "qemu_driver.h" #include "qemu_extdevice.h" #include "qemu_nbdkit.h" +#define LIBVIRT_QEMU_NBDKITPRIV_H_ALLOW +#include "qemu_nbdkitpriv.h" #include "qemu_security.h" #include <fcntl.h> @@ -830,7 +832,7 @@ qemuNbdkitProcessBuildCommandSSH(qemuNbdkitProcess *proc, } -static virCommand * +virCommand * qemuNbdkitProcessBuildCommand(qemuNbdkitProcess *proc) { g_autoptr(virCommand) cmd = virCommandNewArgList(proc->caps->path, diff --git a/src/qemu/qemu_nbdkitpriv.h b/src/qemu/qemu_nbdkitpriv.h new file mode 100644 index 0000000000..64f9bb99d8 --- /dev/null +++ b/src/qemu/qemu_nbdkitpriv.h @@ -0,0 +1,31 @@ +/* + * qemu_nbdkitpriv.h: exposing some functions for testing + * + * Copyright (C) 2021 Red Hat, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see + * <http://www.gnu.org/licenses/>. + * + */ + +#ifndef LIBVIRT_QEMU_NBDKITPRIV_H_ALLOW +# error "qemu_nbdkitpriv.h may only be included by qemu_nbdkit.c or test suites" +#endif /* LIBVIRT_QEMU_NBDKITPRIV_H_ALLOW */ + +#pragma once + +#include "qemu_nbdkit.h" + +virCommand * +qemuNbdkitProcessBuildCommand(qemuNbdkitProcess *proc); diff --git a/tests/meson.build b/tests/meson.build index 1149211756..87b29ab5b4 100644 --- a/tests/meson.build +++ b/tests/meson.build @@ -451,6 +451,7 @@ if conf.has('WITH_QEMU') { 'name': 'qemuvhostusertest', 'link_with': [ test_qemu_driver_lib ], 'link_whole': [ test_file_wrapper_lib ] }, { 'name': 'qemuxml2argvtest', 'link_with': [ test_qemu_driver_lib, test_utils_qemu_monitor_lib ], 'link_whole': [ test_utils_qemu_lib, test_file_wrapper_lib ] }, { 'name': 'qemuxml2xmltest', 'link_with': [ test_qemu_driver_lib ], 'link_whole': [ test_utils_qemu_lib, test_file_wrapper_lib ] }, + { 'name': 'qemunbdkittest', 'link_with': [ test_qemu_driver_lib ], 'link_whole': [ test_utils_qemu_lib, test_file_wrapper_lib ] }, ] endif diff --git a/tests/qemunbdkitdata/disk-network-ssh.args.disk0 b/tests/qemunbdkitdata/disk-network-ssh.args.disk0 new file mode 100644 index 0000000000..e0020dff6a --- /dev/null +++ b/tests/qemunbdkitdata/disk-network-ssh.args.disk0 @@ -0,0 +1,7 @@ +nbdkit \ +--exit-with-parent \ +--unix /tmp/statedir-0/nbdkit-test-disk-0.socket \ +--foreground ssh \ +host=example.org \ +port=2222 \ +path=test.img diff --git a/tests/qemunbdkittest.c b/tests/qemunbdkittest.c new file mode 100644 index 0000000000..c7fa80b9c5 --- /dev/null +++ b/tests/qemunbdkittest.c @@ -0,0 +1,239 @@ +#include <config.h> + +#include "internal.h" +#include "testutils.h" +#include "testutilsqemu.h" +#include "qemu/qemu_domain.h" +#include "qemu/qemu_nbdkit.h" +#define LIBVIRT_QEMU_NBDKITPRIV_H_ALLOW +#include "qemu/qemu_nbdkitpriv.h" +#include "vircommand.h" +#define LIBVIRT_VIRCOMMANDPRIV_H_ALLOW +#include "vircommandpriv.h" +#include "virutil.h" +#include "virsecret.h" +#include "datatypes.h" + +#define VIR_FROM_THIS VIR_FROM_QEMU + +static virQEMUDriver driver; + + +/* Some mock implementations for testing */ +int +virSecretGetSecretString(virConnectPtr conn G_GNUC_UNUSED, + virSecretLookupTypeDef *seclookupdef, + virSecretUsageType secretUsageType, + uint8_t **secret, + size_t *secret_size) +{ + char uuidstr[VIR_UUID_BUFLEN]; + const char *secretname = NULL; + char *tmp = NULL; + + switch (seclookupdef->type) { + case VIR_SECRET_LOOKUP_TYPE_UUID: + virUUIDFormat(seclookupdef->u.uuid, uuidstr); + secretname = uuidstr; + break; + case VIR_SECRET_LOOKUP_TYPE_USAGE: + secretname = seclookupdef->u.usage; + break; + case VIR_SECRET_LOOKUP_TYPE_NONE: + case VIR_SECRET_LOOKUP_TYPE_LAST: + default: + virReportEnumRangeError(virSecretLookupType, seclookupdef->type); + return -1; + }; + + /* For testing, just generate a value for the secret that includes the type + * and the id of the secret */ + tmp = g_strdup_printf("%s-%s-secret", virSecretUsageTypeToString(secretUsageType), secretname); + *secret = (uint8_t*)tmp; + *secret_size = strlen(tmp) + 1; + + return 0; +} + +virConnectPtr virGetConnectSecret(void) +{ + return virGetConnect(); +} + +/* end of mock implementations */ + + +typedef struct { + const char *name; + char* infile; + char* outtemplate; + qemuNbdkitCaps *nbdkitcaps; + bool expectFail; +} TestInfo; + + +typedef enum { + NBDKIT_ARG_CAPS, + NBDKIT_ARG_EXPECT_FAIL, + NBDKIT_ARG_END +} NbdkitArgName; + + +static void +testInfoSetPaths(TestInfo *info) +{ + info->infile = g_strdup_printf("%s/qemuxml2argvdata/%s.xml", + abs_srcdir, info->name); + info->outtemplate = g_strdup_printf("%s/qemunbdkitdata/%s", + abs_srcdir, info->name); +} + +static void +testInfoClear(TestInfo *info) +{ + g_free(info->infile); + g_free(info->outtemplate); + g_clear_object(&info->nbdkitcaps); +} + +static void +testInfoSetArgs(TestInfo *info, ...) +{ + va_list argptr; + NbdkitArgName argname; + unsigned int cap; + + va_start(argptr, info); + while ((argname = va_arg(argptr, NbdkitArgName)) != NBDKIT_ARG_END) { + switch (argname) { + case NBDKIT_ARG_CAPS: + while ((cap = va_arg(argptr, unsigned int)) < QEMU_NBDKIT_CAPS_LAST) + qemuNbdkitCapsSet(info->nbdkitcaps, cap); + break; + case NBDKIT_ARG_EXPECT_FAIL: + info->expectFail = va_arg(argptr, unsigned int); + break; + case NBDKIT_ARG_END: + default: + break; + } + } +} + + +static int +testNbdkit(const void *data) +{ + const TestInfo *info = data; + g_autoptr(virDomainDef) def = NULL; + size_t i; + int ret = 0; + + if (!virFileExists(info->infile)) { + virReportError(VIR_ERR_INTERNAL_ERROR, + "Test input file '%s' is missing", info->infile); + return -1; + } + + if (!(def = virDomainDefParseFile(info->infile, driver.xmlopt, NULL, + VIR_DOMAIN_DEF_PARSE_SKIP_VALIDATE))) + return -1; + + for (i = 0; i < def->ndisks; i++) { + virDomainDiskDef *disk = def->disks[i]; + g_autofree char *statedir = g_strdup_printf("/tmp/statedir-%zi", i); + g_autofree char *alias = g_strdup_printf("test-disk-%zi", i); + g_autofree char *cmdfile = g_strdup_printf("%s.args.disk%zi", + info->outtemplate, i); + + if (qemuNbdkitInitStorageSource(info->nbdkitcaps, disk->src, statedir, + alias, 101, 101)) { + qemuDomainStorageSourcePrivate *srcPriv = + qemuDomainStorageSourcePrivateFetch(disk->src); + g_autoptr(virCommand) cmd = NULL; + g_autoptr(virCommandDryRunToken) dryRunToken = virCommandDryRunTokenNew(); + g_auto(virBuffer) buf = VIR_BUFFER_INITIALIZER; + const char *actualCmdline = NULL; + + virCommandSetDryRun(dryRunToken, &buf, true, true, NULL, NULL); + cmd = qemuNbdkitProcessBuildCommand(srcPriv->nbdkitProcess); + + if (virCommandRun(cmd, NULL) < 0) { + ret = -1; + continue; + } + + if (!(actualCmdline = virBufferContentAndReset(&buf))) { + ret = -1; + continue; + } + + if (virTestCompareToFileFull(actualCmdline, cmdfile, false) < 0) { + ret = -1; + continue; + } + } else { + if (virFileExists(cmdfile)) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + "qemuNbdkitInitStorageSource() was not expected to fail"); + ret = -1; + } + } + } + + if (info->expectFail) { + if (ret == 0) { + ret = -1; + VIR_TEST_DEBUG("Error expected but there wasn't any."); + } else { + ret = 0; + } + } + return ret; +} + +static int +mymain(void) +{ + int ret = 0; + + if (qemuTestDriverInit(&driver) < 0) + return EXIT_FAILURE; + + +#define DO_TEST_FULL(_name, ...) \ + do { \ + TestInfo info = { \ + .name = _name, \ + .nbdkitcaps = qemuNbdkitCapsNew(TEST_NBDKIT_PATH), \ + }; \ + testInfoSetPaths(&info); \ + testInfoSetArgs(&info, __VA_ARGS__); \ + virTestRunLog(&ret, "nbdkit " _name, testNbdkit, &info); \ + testInfoClear(&info); \ + } while (0) + +#define DO_TEST(_name, ...) \ + DO_TEST_FULL(_name, NBDKIT_ARG_CAPS, __VA_ARGS__, QEMU_NBDKIT_CAPS_LAST, NBDKIT_ARG_END) + +#define DO_TEST_FAILURE(_name, ...) \ + DO_TEST_FULL(_name, \ + NBDKIT_ARG_EXPECT_FAIL, 1, \ + NBDKIT_ARG_CAPS, __VA_ARGS__, QEMU_NBDKIT_CAPS_LAST, NBDKIT_ARG_END) + +#define DO_TEST_NOCAPS(_name) \ + DO_TEST_FULL(_name, NBDKIT_ARG_END) + + /* disks with cookies / passwords are not yet supported */ + DO_TEST_FAILURE("disk-cdrom-network", QEMU_NBDKIT_CAPS_PLUGIN_CURL); + DO_TEST_FAILURE("disk-network-http", QEMU_NBDKIT_CAPS_PLUGIN_CURL); + DO_TEST_FAILURE("disk-network-source-curl-nbdkit-backing", QEMU_NBDKIT_CAPS_PLUGIN_CURL); + DO_TEST_FAILURE("disk-network-source-curl", QEMU_NBDKIT_CAPS_PLUGIN_CURL); + DO_TEST("disk-network-ssh", QEMU_NBDKIT_CAPS_PLUGIN_SSH); + + qemuTestDriverFree(&driver); + + return ret == 0 ? EXIT_SUCCESS : EXIT_FAILURE; +} + +VIR_TEST_MAIN(mymain) -- 2.37.3

On Thu, Oct 20, 2022 at 16:59:05 -0500, Jonathon Jongsma wrote:
We were testing the arguments that were being passed to qemu when a disk was being served by nbdkit, but the arguments used to start nbdkit itself were not testable. This adds a test to ensure that we're invoking nbdkit correctly for various disk source definitions.
For now, expect failure for tests that use sensitive data such as passwords and cookies.
Signed-off-by: Jonathon Jongsma <jjongsma@redhat.com> --- src/qemu/qemu_nbdkit.c | 4 +- src/qemu/qemu_nbdkitpriv.h | 31 +++ tests/meson.build | 1 + .../disk-network-ssh.args.disk0 | 7 + tests/qemunbdkittest.c | 239 ++++++++++++++++++ 5 files changed, 281 insertions(+), 1 deletion(-) create mode 100644 src/qemu/qemu_nbdkitpriv.h create mode 100644 tests/qemunbdkitdata/disk-network-ssh.args.disk0 create mode 100644 tests/qemunbdkittest.c
[...]
+#define DO_TEST_NOCAPS(_name) \ + DO_TEST_FULL(_name, NBDKIT_ARG_END) + + /* disks with cookies / passwords are not yet supported */ + DO_TEST_FAILURE("disk-cdrom-network", QEMU_NBDKIT_CAPS_PLUGIN_CURL); + DO_TEST_FAILURE("disk-network-http", QEMU_NBDKIT_CAPS_PLUGIN_CURL); + DO_TEST_FAILURE("disk-network-source-curl-nbdkit-backing", QEMU_NBDKIT_CAPS_PLUGIN_CURL); + DO_TEST_FAILURE("disk-network-source-curl", QEMU_NBDKIT_CAPS_PLUGIN_CURL); + DO_TEST("disk-network-ssh", QEMU_NBDKIT_CAPS_PLUGIN_SSH);
This examplifies my previous comment about functionality being broken. Enabling the nbdkit backend must happen only after full functionality is implemented. For this patch: Reviewed-by: Peter Krempa <pkrempa@redhat.com>

Add a private function to peek at the list of send buffers in virCommand so that it is testable Signed-off-by: Jonathon Jongsma <jjongsma@redhat.com> --- src/libvirt_private.syms | 1 + src/util/vircommand.c | 16 ++++++++-------- src/util/vircommand.h | 8 ++++++++ src/util/vircommandpriv.h | 4 ++++ 4 files changed, 21 insertions(+), 8 deletions(-) diff --git a/src/libvirt_private.syms b/src/libvirt_private.syms index 0b0ccbafe5..bf24d65b08 100644 --- a/src/libvirt_private.syms +++ b/src/libvirt_private.syms @@ -2080,6 +2080,7 @@ virCommandNewArgs; virCommandNewVAList; virCommandNonblockingFDs; virCommandPassFD; +virCommandPeekSendBuffers; virCommandRawStatus; virCommandRequireHandshake; virCommandRun; diff --git a/src/util/vircommand.c b/src/util/vircommand.c index bbfbe19706..014bab9196 100644 --- a/src/util/vircommand.c +++ b/src/util/vircommand.c @@ -77,14 +77,6 @@ struct _virCommandFD { unsigned int flags; }; -typedef struct _virCommandSendBuffer virCommandSendBuffer; -struct _virCommandSendBuffer { - int fd; - unsigned char *buffer; - size_t buflen; - off_t offset; -}; - struct _virCommand { int has_error; /* 0 on success, -1 on error */ @@ -3451,3 +3443,11 @@ virCommandSetRunAmong(virCommand *cmd, cmd->schedCore = pid; } + +void virCommandPeekSendBuffers(virCommand *cmd, + virCommandSendBuffer **buffers, + int *nbuffers) +{ + *buffers = cmd->sendBuffers; + *nbuffers = cmd->numSendBuffers; +} diff --git a/src/util/vircommand.h b/src/util/vircommand.h index 98788bcbf7..fc2ca0f3ff 100644 --- a/src/util/vircommand.h +++ b/src/util/vircommand.h @@ -24,6 +24,14 @@ #include "internal.h" #include "virbuffer.h" +typedef struct _virCommandSendBuffer virCommandSendBuffer; +struct _virCommandSendBuffer { + int fd; + unsigned char *buffer; + size_t buflen; + off_t offset; +}; + typedef struct _virCommand virCommand; /* This will execute in the context of the first child diff --git a/src/util/vircommandpriv.h b/src/util/vircommandpriv.h index ff17fa5ded..d579810bb5 100644 --- a/src/util/vircommandpriv.h +++ b/src/util/vircommandpriv.h @@ -47,3 +47,7 @@ void virCommandSetDryRun(virCommandDryRunToken *tok, bool bufCommandStripPath, virCommandDryRunCallback cb, void *opaque); + +void virCommandPeekSendBuffers(virCommand *cmd, + virCommandSendBuffer **buffers, + int *nbuffers); -- 2.37.3

On Thu, Oct 20, 2022 at 16:59:06 -0500, Jonathon Jongsma wrote:
Add a private function to peek at the list of send buffers in virCommand so that it is testable
Signed-off-by: Jonathon Jongsma <jjongsma@redhat.com> --- src/libvirt_private.syms | 1 + src/util/vircommand.c | 16 ++++++++-------- src/util/vircommand.h | 8 ++++++++ src/util/vircommandpriv.h | 4 ++++ 4 files changed, 21 insertions(+), 8 deletions(-)
diff --git a/src/libvirt_private.syms b/src/libvirt_private.syms index 0b0ccbafe5..bf24d65b08 100644 --- a/src/libvirt_private.syms +++ b/src/libvirt_private.syms @@ -2080,6 +2080,7 @@ virCommandNewArgs; virCommandNewVAList; virCommandNonblockingFDs; virCommandPassFD; +virCommandPeekSendBuffers; virCommandRawStatus; virCommandRequireHandshake; virCommandRun; diff --git a/src/util/vircommand.c b/src/util/vircommand.c index bbfbe19706..014bab9196 100644 --- a/src/util/vircommand.c +++ b/src/util/vircommand.c @@ -77,14 +77,6 @@ struct _virCommandFD { unsigned int flags; };
-typedef struct _virCommandSendBuffer virCommandSendBuffer; -struct _virCommandSendBuffer { - int fd; - unsigned char *buffer; - size_t buflen; - off_t offset; -}; - struct _virCommand { int has_error; /* 0 on success, -1 on error */
@@ -3451,3 +3443,11 @@ virCommandSetRunAmong(virCommand *cmd,
cmd->schedCore = pid; } + +void virCommandPeekSendBuffers(virCommand *cmd, + virCommandSendBuffer **buffers, + int *nbuffers)
Header formatting does not conform to the rest of the file.
+{ + *buffers = cmd->sendBuffers; + *nbuffers = cmd->numSendBuffers; +}
With the above addressed: Reviewed-by: Peter Krempa <pkrempa@redhat.com>

Rather than passing passwords and cookies (which could contain passwords) to nbdkit via commandline arguments, use the alternate format that nbdkit supports where we can specify a file descriptor which nbdkit will read to get the password or cookies. Signed-off-by: Jonathon Jongsma <jjongsma@redhat.com> --- build-aux/syntax-check.mk | 4 +- src/qemu/qemu_nbdkit.c | 64 ++++++++++++----- src/util/vircommand.c | 3 +- src/util/virutil.h | 2 +- .../disk-cdrom-network.args.disk0 | 7 ++ .../disk-cdrom-network.args.disk1 | 9 +++ .../disk-cdrom-network.args.disk1.pipe.1778 | 1 + .../disk-cdrom-network.args.disk2 | 9 +++ .../disk-cdrom-network.args.disk2.pipe.1780 | 1 + .../disk-network-http.args.disk0 | 7 ++ .../disk-network-http.args.disk1 | 6 ++ .../disk-network-http.args.disk2 | 7 ++ .../disk-network-http.args.disk2.pipe.1778 | 1 + .../disk-network-http.args.disk3 | 8 +++ .../disk-network-http.args.disk3.pipe.1780 | 1 + ...work-source-curl-nbdkit-backing.args.disk0 | 8 +++ ...e-curl-nbdkit-backing.args.disk0.pipe.1778 | 1 + .../disk-network-source-curl.args.disk0 | 8 +++ ...k-network-source-curl.args.disk0.pipe.1778 | 1 + .../disk-network-source-curl.args.disk1 | 8 +++ ...k-network-source-curl.args.disk1.pipe.1780 | 1 + .../disk-network-source-curl.args.disk2 | 8 +++ ...k-network-source-curl.args.disk2.pipe.1782 | 1 + .../disk-network-source-curl.args.disk3 | 7 ++ .../disk-network-source-curl.args.disk4 | 7 ++ tests/qemunbdkittest.c | 69 +++++++++++++++++-- 26 files changed, 219 insertions(+), 30 deletions(-) create mode 100644 tests/qemunbdkitdata/disk-cdrom-network.args.disk0 create mode 100644 tests/qemunbdkitdata/disk-cdrom-network.args.disk1 create mode 100644 tests/qemunbdkitdata/disk-cdrom-network.args.disk1.pipe.1778 create mode 100644 tests/qemunbdkitdata/disk-cdrom-network.args.disk2 create mode 100644 tests/qemunbdkitdata/disk-cdrom-network.args.disk2.pipe.1780 create mode 100644 tests/qemunbdkitdata/disk-network-http.args.disk0 create mode 100644 tests/qemunbdkitdata/disk-network-http.args.disk1 create mode 100644 tests/qemunbdkitdata/disk-network-http.args.disk2 create mode 100644 tests/qemunbdkitdata/disk-network-http.args.disk2.pipe.1778 create mode 100644 tests/qemunbdkitdata/disk-network-http.args.disk3 create mode 100644 tests/qemunbdkitdata/disk-network-http.args.disk3.pipe.1780 create mode 100644 tests/qemunbdkitdata/disk-network-source-curl-nbdkit-backing.args.disk0 create mode 100644 tests/qemunbdkitdata/disk-network-source-curl-nbdkit-backing.args.disk0.pipe.1778 create mode 100644 tests/qemunbdkitdata/disk-network-source-curl.args.disk0 create mode 100644 tests/qemunbdkitdata/disk-network-source-curl.args.disk0.pipe.1778 create mode 100644 tests/qemunbdkitdata/disk-network-source-curl.args.disk1 create mode 100644 tests/qemunbdkitdata/disk-network-source-curl.args.disk1.pipe.1780 create mode 100644 tests/qemunbdkitdata/disk-network-source-curl.args.disk2 create mode 100644 tests/qemunbdkitdata/disk-network-source-curl.args.disk2.pipe.1782 create mode 100644 tests/qemunbdkitdata/disk-network-source-curl.args.disk3 create mode 100644 tests/qemunbdkitdata/disk-network-source-curl.args.disk4 diff --git a/build-aux/syntax-check.mk b/build-aux/syntax-check.mk index 68cd9dff5f..d44b1e5b17 100644 --- a/build-aux/syntax-check.mk +++ b/build-aux/syntax-check.mk @@ -1355,10 +1355,10 @@ exclude_file_name_regexp--sc_prohibit_strdup = \ ^(docs/|examples/|tests/virnetserverclientmock.c|tests/commandhelper.c|tools/nss/libvirt_nss_(leases|macs)\.c$$) exclude_file_name_regexp--sc_prohibit_close = \ - (\.p[yl]$$|\.spec\.in$$|^docs/|^(src/util/vir(file|event)\.c|src/libvirt-stream\.c|tests/(vir.+mock\.c|commandhelper\.c|qemusecuritymock\.c)|tools/nss/libvirt_nss_(leases|macs)\.c)|tools/virt-qemu-qmp-proxy$$) + (\.p[yl]$$|\.spec\.in$$|^docs/|^(src/util/vir(file|event)\.c|src/libvirt-stream\.c|tests/(vir.+mock\.c|commandhelper\.c|qemusecuritymock\.c|qemunbdkittest\.c)|tools/nss/libvirt_nss_(leases|macs)\.c)|tools/virt-qemu-qmp-proxy$$) exclude_file_name_regexp--sc_prohibit_empty_lines_at_EOF = \ - (^tests/(nodedevmdevctl|virhostcpu|virpcitest|virstoragetest)data/|docs/js/.*\.js|docs/fonts/.*\.woff|\.diff|tests/virconfdata/no-newline\.conf$$) + (^tests/(nodedevmdevctl|virhostcpu|virpcitest|virstoragetest|qemunbdkit)data/|docs/js/.*\.js|docs/fonts/.*\.woff|\.diff|tests/virconfdata/no-newline\.conf$$) exclude_file_name_regexp--sc_prohibit_fork_wrappers = \ (^(src/(util/(vircommand|virdaemon)|lxc/lxc_controller)|tests/testutils)\.c$$) diff --git a/src/qemu/qemu_nbdkit.c b/src/qemu/qemu_nbdkit.c index 882a074211..0a0dc5d2a4 100644 --- a/src/qemu/qemu_nbdkit.c +++ b/src/qemu/qemu_nbdkit.c @@ -55,6 +55,7 @@ VIR_ENUM_IMPL(qemuNbdkitCaps, "filter-readahead", /* QEMU_NBDKIT_CAPS_FILTER_READAHEAD */ ); + struct _qemuNbdkitCaps { GObject parent; @@ -71,6 +72,12 @@ struct _qemuNbdkitCaps { G_DEFINE_TYPE(qemuNbdkitCaps, qemu_nbdkit_caps, G_TYPE_OBJECT); +enum { + PIPE_FD_READ = 0, + PIPE_FD_WRITE = 1 +}; + + static void qemuNbdkitCheckCommandCap(qemuNbdkitCaps *nbdkit, virCommand *cmd, @@ -729,6 +736,29 @@ qemuNbdkitStopStorageSource(virStorageSource *src) } +static int +qemuNbdkitCommandPassDataByPipe(virCommand *cmd, + const char *argName, + unsigned char *buf, + size_t buflen) +{ + g_autofree char *fdfmt = NULL; + int fd = virCommandSetSendBuffer(cmd, buf, buflen); + + if (fd < 0) + return -1; + + /* some nbdkit arguments accept a variation where nbdkit will read the data + * from a file descriptor, e.g. password=-FD */ + fdfmt = g_strdup_printf("-%i", fd); + virCommandAddArgPair(cmd, argName, fdfmt); + + virCommandDoAsyncIO(cmd); + + return 0; +} + + static int qemuNbdkitProcessBuildCommandCurl(qemuNbdkitProcess *proc, virCommand *cmd) @@ -744,10 +774,10 @@ qemuNbdkitProcessBuildCommandCurl(qemuNbdkitProcess *proc, if (proc->source->auth) { g_autoptr(virConnect) conn = virGetConnectSecret(); - g_autofree uint8_t *secret = NULL; + uint8_t *secret = NULL; size_t secretlen = 0; - g_autofree char *password = NULL; int secrettype; + virStorageAuthDef *authdef = proc->source->auth; virCommandAddArgPair(cmd, "user", proc->source->auth->username); @@ -760,7 +790,7 @@ qemuNbdkitProcessBuildCommandCurl(qemuNbdkitProcess *proc, } if (virSecretGetSecretString(conn, - &proc->source->auth->seclookupdef, + &authdef->seclookupdef, secrettype, &secret, &secretlen) < 0) { @@ -769,24 +799,20 @@ qemuNbdkitProcessBuildCommandCurl(qemuNbdkitProcess *proc, return -1; } - /* ensure that the secret is a NULL-terminated string */ - password = g_strndup((char*)secret, secretlen); - - /* for now, just report an error rather than passing the password in - * cleartext on the commandline */ - virReportError(VIR_ERR_INTERNAL_ERROR, - "%s", - "Password not yet supported for nbdkit sources"); - return -1; + if (qemuNbdkitCommandPassDataByPipe(cmd, "password", + secret, secretlen) < 0) + return -1; } - if (proc->source->ncookies > 0) { - /* for now, just report an error rather than passing cookies in - * cleartext on the commandline */ - virReportError(VIR_ERR_INTERNAL_ERROR, - "%s", - "Cookies not yet supported for nbdkit sources"); - return -1; + /* Create a pipe to send the cookies to the nbdkit process. */ + if (proc->source->ncookies) { + char *cookies = + qemuBlockStorageSourceGetCookieString(proc->source); + + if (qemuNbdkitCommandPassDataByPipe(cmd, "cookie", + (unsigned char*)cookies, + strlen(cookies)) < 0) + return -1; } if (proc->source->sslverify == VIR_TRISTATE_BOOL_NO) { diff --git a/src/util/vircommand.c b/src/util/vircommand.c index 014bab9196..838eb6bd16 100644 --- a/src/util/vircommand.c +++ b/src/util/vircommand.c @@ -1703,7 +1703,8 @@ virCommandSetSendBuffer(virCommand *cmd, return -1; } - if (fcntl(pipefd[1], F_SETFL, O_NONBLOCK) < 0) { + if (!(dryRunBuffer || dryRunCallback) && + fcntl(pipefd[1], F_SETFL, O_NONBLOCK) < 0) { cmd->has_error = errno; VIR_FORCE_CLOSE(pipefd[0]); VIR_FORCE_CLOSE(pipefd[1]); diff --git a/src/util/virutil.h b/src/util/virutil.h index ab8511bf8d..094b399859 100644 --- a/src/util/virutil.h +++ b/src/util/virutil.h @@ -186,7 +186,7 @@ char *virGetPassword(void); * * Returns: -1 on error, 0 on success */ -int virPipe(int fds[2]); +int virPipe(int fds[2]) G_NO_INLINE; /* * virPipeQuiet: diff --git a/tests/qemunbdkitdata/disk-cdrom-network.args.disk0 b/tests/qemunbdkitdata/disk-cdrom-network.args.disk0 new file mode 100644 index 0000000000..5f3a795ba0 --- /dev/null +++ b/tests/qemunbdkitdata/disk-cdrom-network.args.disk0 @@ -0,0 +1,7 @@ +nbdkit \ +--exit-with-parent \ +--unix /tmp/statedir-0/nbdkit-test-disk-0.socket \ +--foreground \ +--readonly curl \ +protocols=ftp \ +url=ftp://host.name:21/url/path/file.iso diff --git a/tests/qemunbdkitdata/disk-cdrom-network.args.disk1 b/tests/qemunbdkitdata/disk-cdrom-network.args.disk1 new file mode 100644 index 0000000000..12c0dcaf0e --- /dev/null +++ b/tests/qemunbdkitdata/disk-cdrom-network.args.disk1 @@ -0,0 +1,9 @@ +nbdkit \ +--exit-with-parent \ +--unix /tmp/statedir-1/nbdkit-test-disk-1.socket \ +--foreground \ +--readonly curl \ +protocols=ftps \ +url=ftps://host.name:990/url/path/file.iso \ +user=testuser \ +password=-1777 diff --git a/tests/qemunbdkitdata/disk-cdrom-network.args.disk1.pipe.1778 b/tests/qemunbdkitdata/disk-cdrom-network.args.disk1.pipe.1778 new file mode 100644 index 0000000000..ccdd4033fc --- /dev/null +++ b/tests/qemunbdkitdata/disk-cdrom-network.args.disk1.pipe.1778 @@ -0,0 +1 @@ +iscsi-mycluster_myname-secret \ No newline at end of file diff --git a/tests/qemunbdkitdata/disk-cdrom-network.args.disk2 b/tests/qemunbdkitdata/disk-cdrom-network.args.disk2 new file mode 100644 index 0000000000..d41337a089 --- /dev/null +++ b/tests/qemunbdkitdata/disk-cdrom-network.args.disk2 @@ -0,0 +1,9 @@ +nbdkit \ +--exit-with-parent \ +--unix /tmp/statedir-2/nbdkit-test-disk-2.socket \ +--foreground \ +--readonly curl \ +protocols=https \ +'url=https://host.name:443/url/path/file.iso?test=val' \ +user=testuser \ +password=-1779 diff --git a/tests/qemunbdkitdata/disk-cdrom-network.args.disk2.pipe.1780 b/tests/qemunbdkitdata/disk-cdrom-network.args.disk2.pipe.1780 new file mode 100644 index 0000000000..ccdd4033fc --- /dev/null +++ b/tests/qemunbdkitdata/disk-cdrom-network.args.disk2.pipe.1780 @@ -0,0 +1 @@ +iscsi-mycluster_myname-secret \ No newline at end of file diff --git a/tests/qemunbdkitdata/disk-network-http.args.disk0 b/tests/qemunbdkitdata/disk-network-http.args.disk0 new file mode 100644 index 0000000000..fa8ef90cd1 --- /dev/null +++ b/tests/qemunbdkitdata/disk-network-http.args.disk0 @@ -0,0 +1,7 @@ +nbdkit \ +--exit-with-parent \ +--unix /tmp/statedir-0/nbdkit-test-disk-0.socket \ +--foreground curl \ +protocols=http \ +url=http://example.org:80/test.img \ +timeout=1234 diff --git a/tests/qemunbdkitdata/disk-network-http.args.disk1 b/tests/qemunbdkitdata/disk-network-http.args.disk1 new file mode 100644 index 0000000000..9bac3fe229 --- /dev/null +++ b/tests/qemunbdkitdata/disk-network-http.args.disk1 @@ -0,0 +1,6 @@ +nbdkit \ +--exit-with-parent \ +--unix /tmp/statedir-1/nbdkit-test-disk-1.socket \ +--foreground curl \ +protocols=https \ +url=https://example.org:443/test2.img diff --git a/tests/qemunbdkitdata/disk-network-http.args.disk2 b/tests/qemunbdkitdata/disk-network-http.args.disk2 new file mode 100644 index 0000000000..2d39b6c259 --- /dev/null +++ b/tests/qemunbdkitdata/disk-network-http.args.disk2 @@ -0,0 +1,7 @@ +nbdkit \ +--exit-with-parent \ +--unix /tmp/statedir-2/nbdkit-test-disk-2.socket \ +--foreground curl \ +protocols=http \ +url=http://example.org:1234/test3.img \ +cookie=-1777 diff --git a/tests/qemunbdkitdata/disk-network-http.args.disk2.pipe.1778 b/tests/qemunbdkitdata/disk-network-http.args.disk2.pipe.1778 new file mode 100644 index 0000000000..2c42c95930 --- /dev/null +++ b/tests/qemunbdkitdata/disk-network-http.args.disk2.pipe.1778 @@ -0,0 +1 @@ +test=testcookievalue; test2="blurb" \ No newline at end of file diff --git a/tests/qemunbdkitdata/disk-network-http.args.disk3 b/tests/qemunbdkitdata/disk-network-http.args.disk3 new file mode 100644 index 0000000000..54f12f5c9e --- /dev/null +++ b/tests/qemunbdkitdata/disk-network-http.args.disk3 @@ -0,0 +1,8 @@ +nbdkit \ +--exit-with-parent \ +--unix /tmp/statedir-3/nbdkit-test-disk-3.socket \ +--foreground curl \ +protocols=https \ +'url=https://example.org:1234/test4.img?par=val&other=ble' \ +cookie=-1779 \ +sslverify=false diff --git a/tests/qemunbdkitdata/disk-network-http.args.disk3.pipe.1780 b/tests/qemunbdkitdata/disk-network-http.args.disk3.pipe.1780 new file mode 100644 index 0000000000..2c42c95930 --- /dev/null +++ b/tests/qemunbdkitdata/disk-network-http.args.disk3.pipe.1780 @@ -0,0 +1 @@ +test=testcookievalue; test2="blurb" \ No newline at end of file diff --git a/tests/qemunbdkitdata/disk-network-source-curl-nbdkit-backing.args.disk0 b/tests/qemunbdkitdata/disk-network-source-curl-nbdkit-backing.args.disk0 new file mode 100644 index 0000000000..eb479b996f --- /dev/null +++ b/tests/qemunbdkitdata/disk-network-source-curl-nbdkit-backing.args.disk0 @@ -0,0 +1,8 @@ +nbdkit \ +--exit-with-parent \ +--unix /tmp/statedir-0/nbdkit-test-disk-0.socket \ +--foreground \ +--readonly curl \ +protocols=https \ +url=https://https.example.org:8443/path/to/disk1.qcow2 \ +cookie=-1777 diff --git a/tests/qemunbdkitdata/disk-network-source-curl-nbdkit-backing.args.disk0.pipe.1778 b/tests/qemunbdkitdata/disk-network-source-curl-nbdkit-backing.args.disk0.pipe.1778 new file mode 100644 index 0000000000..20af4ae383 --- /dev/null +++ b/tests/qemunbdkitdata/disk-network-source-curl-nbdkit-backing.args.disk0.pipe.1778 @@ -0,0 +1 @@ +cookie1=cookievalue1; cookie2=cookievalue2 \ No newline at end of file diff --git a/tests/qemunbdkitdata/disk-network-source-curl.args.disk0 b/tests/qemunbdkitdata/disk-network-source-curl.args.disk0 new file mode 100644 index 0000000000..cf2c0b7184 --- /dev/null +++ b/tests/qemunbdkitdata/disk-network-source-curl.args.disk0 @@ -0,0 +1,8 @@ +nbdkit \ +--exit-with-parent \ +--unix /tmp/statedir-0/nbdkit-test-disk-0.socket \ +--foreground \ +--readonly curl \ +protocols=https \ +url=https://https.example.org:8443/path/to/disk1.iso \ +cookie=-1777 diff --git a/tests/qemunbdkitdata/disk-network-source-curl.args.disk0.pipe.1778 b/tests/qemunbdkitdata/disk-network-source-curl.args.disk0.pipe.1778 new file mode 100644 index 0000000000..20af4ae383 --- /dev/null +++ b/tests/qemunbdkitdata/disk-network-source-curl.args.disk0.pipe.1778 @@ -0,0 +1 @@ +cookie1=cookievalue1; cookie2=cookievalue2 \ No newline at end of file diff --git a/tests/qemunbdkitdata/disk-network-source-curl.args.disk1 b/tests/qemunbdkitdata/disk-network-source-curl.args.disk1 new file mode 100644 index 0000000000..13f03c545e --- /dev/null +++ b/tests/qemunbdkitdata/disk-network-source-curl.args.disk1 @@ -0,0 +1,8 @@ +nbdkit \ +--exit-with-parent \ +--unix /tmp/statedir-1/nbdkit-test-disk-1.socket \ +--foreground curl \ +protocols=https \ +'url=https://https.example.org:8443/path/to/disk5.iso?foo=bar' \ +cookie=-1779 \ +sslverify=false diff --git a/tests/qemunbdkitdata/disk-network-source-curl.args.disk1.pipe.1780 b/tests/qemunbdkitdata/disk-network-source-curl.args.disk1.pipe.1780 new file mode 100644 index 0000000000..20af4ae383 --- /dev/null +++ b/tests/qemunbdkitdata/disk-network-source-curl.args.disk1.pipe.1780 @@ -0,0 +1 @@ +cookie1=cookievalue1; cookie2=cookievalue2 \ No newline at end of file diff --git a/tests/qemunbdkitdata/disk-network-source-curl.args.disk2 b/tests/qemunbdkitdata/disk-network-source-curl.args.disk2 new file mode 100644 index 0000000000..490aea3393 --- /dev/null +++ b/tests/qemunbdkitdata/disk-network-source-curl.args.disk2 @@ -0,0 +1,8 @@ +nbdkit \ +--exit-with-parent \ +--unix /tmp/statedir-2/nbdkit-test-disk-2.socket \ +--foreground \ +--readonly curl \ +protocols=http \ +url=http://http.example.org:8080/path/to/disk2.iso \ +cookie=-1781 diff --git a/tests/qemunbdkitdata/disk-network-source-curl.args.disk2.pipe.1782 b/tests/qemunbdkitdata/disk-network-source-curl.args.disk2.pipe.1782 new file mode 100644 index 0000000000..5c035e84c5 --- /dev/null +++ b/tests/qemunbdkitdata/disk-network-source-curl.args.disk2.pipe.1782 @@ -0,0 +1 @@ +cookie1=cookievalue1; cookie2=cookievalue2; cookie3=cookievalue3 \ No newline at end of file diff --git a/tests/qemunbdkitdata/disk-network-source-curl.args.disk3 b/tests/qemunbdkitdata/disk-network-source-curl.args.disk3 new file mode 100644 index 0000000000..bc28f04564 --- /dev/null +++ b/tests/qemunbdkitdata/disk-network-source-curl.args.disk3 @@ -0,0 +1,7 @@ +nbdkit \ +--exit-with-parent \ +--unix /tmp/statedir-3/nbdkit-test-disk-3.socket \ +--foreground \ +--readonly curl \ +protocols=ftp \ +url=ftp://ftp.example.org:20/path/to/disk3.iso diff --git a/tests/qemunbdkitdata/disk-network-source-curl.args.disk4 b/tests/qemunbdkitdata/disk-network-source-curl.args.disk4 new file mode 100644 index 0000000000..7c3cc711ae --- /dev/null +++ b/tests/qemunbdkitdata/disk-network-source-curl.args.disk4 @@ -0,0 +1,7 @@ +nbdkit \ +--exit-with-parent \ +--unix /tmp/statedir-4/nbdkit-test-disk-4.socket \ +--foreground \ +--readonly curl \ +protocols=ftps \ +url=ftps://ftps.example.org:22/path/to/disk4.iso diff --git a/tests/qemunbdkittest.c b/tests/qemunbdkittest.c index c7fa80b9c5..49888ab8a1 100644 --- a/tests/qemunbdkittest.c +++ b/tests/qemunbdkittest.c @@ -1,5 +1,6 @@ #include <config.h> +#include <fcntl.h> #include "internal.h" #include "testutils.h" #include "testutilsqemu.h" @@ -13,6 +14,7 @@ #include "virutil.h" #include "virsecret.h" #include "datatypes.h" +#include "virmock.h" #define VIR_FROM_THIS VIR_FROM_QEMU @@ -20,6 +22,45 @@ static virQEMUDriver driver; /* Some mock implementations for testing */ +#define PIPE_FD_START 1777 +static int mockpipefd = PIPE_FD_START; +int +virPipeQuiet(int fds[2]) +{ + fds[0] = mockpipefd++; + fds[1] = mockpipefd++; + + if (fcntl(fds[0], F_GETFD) != -1 || + fcntl(fds[1], F_GETFD) != -1) + abort(); + + return 0; +} + +static int (*real_close)(int fd); +static void +init_syms(void) +{ + VIR_MOCK_REAL_INIT(close); +} + +int +close(int fd) +{ + int ret; + + init_syms(); + + if (fd >= PIPE_FD_START) + ret = 0; + else + ret = real_close(fd); + + return ret; +} + + + int virSecretGetSecretString(virConnectPtr conn G_GNUC_UNUSED, virSecretLookupTypeDef *seclookupdef, @@ -129,6 +170,9 @@ testNbdkit(const void *data) size_t i; int ret = 0; + /* restart mock pipe fds so tests are consistent */ + mockpipefd = PIPE_FD_START; + if (!virFileExists(info->infile)) { virReportError(VIR_ERR_INTERNAL_ERROR, "Test input file '%s' is missing", info->infile); @@ -154,6 +198,9 @@ testNbdkit(const void *data) g_autoptr(virCommandDryRunToken) dryRunToken = virCommandDryRunTokenNew(); g_auto(virBuffer) buf = VIR_BUFFER_INITIALIZER; const char *actualCmdline = NULL; + virCommandSendBuffer *sendbuffers; + int nsendbuffers; + size_t j; virCommandSetDryRun(dryRunToken, &buf, true, true, NULL, NULL); cmd = qemuNbdkitProcessBuildCommand(srcPriv->nbdkitProcess); @@ -162,15 +209,24 @@ testNbdkit(const void *data) ret = -1; continue; } + virCommandPeekSendBuffers(cmd, &sendbuffers, &nsendbuffers); if (!(actualCmdline = virBufferContentAndReset(&buf))) { ret = -1; continue; } - if (virTestCompareToFileFull(actualCmdline, cmdfile, false) < 0) { + if (virTestCompareToFileFull(actualCmdline, cmdfile, false) < 0) ret = -1; - continue; + + for (j = 0; j < nsendbuffers; j++) { + virCommandSendBuffer *buffer = &sendbuffers[j]; + g_autofree char *pipefile = g_strdup_printf("%s.pipe.%i", + cmdfile, + buffer->fd); + + if (virTestCompareToFile((const char*)buffer->buffer, pipefile) < 0) + ret = -1; } } else { if (virFileExists(cmdfile)) { @@ -224,11 +280,10 @@ mymain(void) #define DO_TEST_NOCAPS(_name) \ DO_TEST_FULL(_name, NBDKIT_ARG_END) - /* disks with cookies / passwords are not yet supported */ - DO_TEST_FAILURE("disk-cdrom-network", QEMU_NBDKIT_CAPS_PLUGIN_CURL); - DO_TEST_FAILURE("disk-network-http", QEMU_NBDKIT_CAPS_PLUGIN_CURL); - DO_TEST_FAILURE("disk-network-source-curl-nbdkit-backing", QEMU_NBDKIT_CAPS_PLUGIN_CURL); - DO_TEST_FAILURE("disk-network-source-curl", QEMU_NBDKIT_CAPS_PLUGIN_CURL); + DO_TEST("disk-cdrom-network", QEMU_NBDKIT_CAPS_PLUGIN_CURL); + DO_TEST("disk-network-http", QEMU_NBDKIT_CAPS_PLUGIN_CURL); + DO_TEST("disk-network-source-curl-nbdkit-backing", QEMU_NBDKIT_CAPS_PLUGIN_CURL); + DO_TEST("disk-network-source-curl", QEMU_NBDKIT_CAPS_PLUGIN_CURL); DO_TEST("disk-network-ssh", QEMU_NBDKIT_CAPS_PLUGIN_SSH); qemuTestDriverFree(&driver); -- 2.37.3

On Thu, Oct 20, 2022 at 16:59:07 -0500, Jonathon Jongsma wrote:
Rather than passing passwords and cookies (which could contain passwords) to nbdkit via commandline arguments, use the alternate format that nbdkit supports where we can specify a file descriptor which nbdkit will read to get the password or cookies.
Signed-off-by: Jonathon Jongsma <jjongsma@redhat.com> --- build-aux/syntax-check.mk | 4 +- src/qemu/qemu_nbdkit.c | 64 ++++++++++++----- src/util/vircommand.c | 3 +- src/util/virutil.h | 2 +- .../disk-cdrom-network.args.disk0 | 7 ++ .../disk-cdrom-network.args.disk1 | 9 +++ .../disk-cdrom-network.args.disk1.pipe.1778 | 1 + .../disk-cdrom-network.args.disk2 | 9 +++ .../disk-cdrom-network.args.disk2.pipe.1780 | 1 + .../disk-network-http.args.disk0 | 7 ++ .../disk-network-http.args.disk1 | 6 ++ .../disk-network-http.args.disk2 | 7 ++ .../disk-network-http.args.disk2.pipe.1778 | 1 + .../disk-network-http.args.disk3 | 8 +++ .../disk-network-http.args.disk3.pipe.1780 | 1 + ...work-source-curl-nbdkit-backing.args.disk0 | 8 +++ ...e-curl-nbdkit-backing.args.disk0.pipe.1778 | 1 + .../disk-network-source-curl.args.disk0 | 8 +++ ...k-network-source-curl.args.disk0.pipe.1778 | 1 + .../disk-network-source-curl.args.disk1 | 8 +++ ...k-network-source-curl.args.disk1.pipe.1780 | 1 + .../disk-network-source-curl.args.disk2 | 8 +++ ...k-network-source-curl.args.disk2.pipe.1782 | 1 + .../disk-network-source-curl.args.disk3 | 7 ++ .../disk-network-source-curl.args.disk4 | 7 ++ tests/qemunbdkittest.c | 69 +++++++++++++++++-- 26 files changed, 219 insertions(+), 30 deletions(-) create mode 100644 tests/qemunbdkitdata/disk-cdrom-network.args.disk0 create mode 100644 tests/qemunbdkitdata/disk-cdrom-network.args.disk1 create mode 100644 tests/qemunbdkitdata/disk-cdrom-network.args.disk1.pipe.1778 create mode 100644 tests/qemunbdkitdata/disk-cdrom-network.args.disk2 create mode 100644 tests/qemunbdkitdata/disk-cdrom-network.args.disk2.pipe.1780 create mode 100644 tests/qemunbdkitdata/disk-network-http.args.disk0 create mode 100644 tests/qemunbdkitdata/disk-network-http.args.disk1 create mode 100644 tests/qemunbdkitdata/disk-network-http.args.disk2 create mode 100644 tests/qemunbdkitdata/disk-network-http.args.disk2.pipe.1778 create mode 100644 tests/qemunbdkitdata/disk-network-http.args.disk3 create mode 100644 tests/qemunbdkitdata/disk-network-http.args.disk3.pipe.1780 create mode 100644 tests/qemunbdkitdata/disk-network-source-curl-nbdkit-backing.args.disk0 create mode 100644 tests/qemunbdkitdata/disk-network-source-curl-nbdkit-backing.args.disk0.pipe.1778 create mode 100644 tests/qemunbdkitdata/disk-network-source-curl.args.disk0 create mode 100644 tests/qemunbdkitdata/disk-network-source-curl.args.disk0.pipe.1778 create mode 100644 tests/qemunbdkitdata/disk-network-source-curl.args.disk1 create mode 100644 tests/qemunbdkitdata/disk-network-source-curl.args.disk1.pipe.1780 create mode 100644 tests/qemunbdkitdata/disk-network-source-curl.args.disk2 create mode 100644 tests/qemunbdkitdata/disk-network-source-curl.args.disk2.pipe.1782 create mode 100644 tests/qemunbdkitdata/disk-network-source-curl.args.disk3 create mode 100644 tests/qemunbdkitdata/disk-network-source-curl.args.disk4
diff --git a/build-aux/syntax-check.mk b/build-aux/syntax-check.mk index 68cd9dff5f..d44b1e5b17 100644 --- a/build-aux/syntax-check.mk +++ b/build-aux/syntax-check.mk @@ -1355,10 +1355,10 @@ exclude_file_name_regexp--sc_prohibit_strdup = \ ^(docs/|examples/|tests/virnetserverclientmock.c|tests/commandhelper.c|tools/nss/libvirt_nss_(leases|macs)\.c$$)
exclude_file_name_regexp--sc_prohibit_close = \ - (\.p[yl]$$|\.spec\.in$$|^docs/|^(src/util/vir(file|event)\.c|src/libvirt-stream\.c|tests/(vir.+mock\.c|commandhelper\.c|qemusecuritymock\.c)|tools/nss/libvirt_nss_(leases|macs)\.c)|tools/virt-qemu-qmp-proxy$$) + (\.p[yl]$$|\.spec\.in$$|^docs/|^(src/util/vir(file|event)\.c|src/libvirt-stream\.c|tests/(vir.+mock\.c|commandhelper\.c|qemusecuritymock\.c|qemunbdkittest\.c)|tools/nss/libvirt_nss_(leases|macs)\.c)|tools/virt-qemu-qmp-proxy$$)
In other cases we simply close a non-existing FD, so this might not be needed.
@@ -729,6 +736,29 @@ qemuNbdkitStopStorageSource(virStorageSource *src) }
+static int +qemuNbdkitCommandPassDataByPipe(virCommand *cmd, + const char *argName, + unsigned char *buf, + size_t buflen) +{ + g_autofree char *fdfmt = NULL; + int fd = virCommandSetSendBuffer(cmd, buf, buflen);
This function now takes a unsigned char ** for 'buf' and clears it. You'll need to adapt the prototype.
+ + if (fd < 0) + return -1; + + /* some nbdkit arguments accept a variation where nbdkit will read the data + * from a file descriptor, e.g. password=-FD */ + fdfmt = g_strdup_printf("-%i", fd); + virCommandAddArgPair(cmd, argName, fdfmt); + + virCommandDoAsyncIO(cmd); + + return 0; +} + + static int qemuNbdkitProcessBuildCommandCurl(qemuNbdkitProcess *proc, virCommand *cmd) @@ -744,10 +774,10 @@ qemuNbdkitProcessBuildCommandCurl(qemuNbdkitProcess *proc,
if (proc->source->auth) { g_autoptr(virConnect) conn = virGetConnectSecret(); - g_autofree uint8_t *secret = NULL; + uint8_t *secret = NULL;
... and then you can keep this here because it will be cleared approrpately.
size_t secretlen = 0; - g_autofree char *password = NULL; int secrettype; + virStorageAuthDef *authdef = proc->source->auth;
virCommandAddArgPair(cmd, "user", proc->source->auth->username); @@ -760,7 +790,7 @@ qemuNbdkitProcessBuildCommandCurl(qemuNbdkitProcess *proc, }
if (virSecretGetSecretString(conn, - &proc->source->auth->seclookupdef, + &authdef->seclookupdef, secrettype, &secret, &secretlen) < 0) {
This hunk belongs to the commit adding this bit.
@@ -769,24 +799,20 @@ qemuNbdkitProcessBuildCommandCurl(qemuNbdkitProcess *proc, return -1; }
- /* ensure that the secret is a NULL-terminated string */ - password = g_strndup((char*)secret, secretlen); - - /* for now, just report an error rather than passing the password in - * cleartext on the commandline */ - virReportError(VIR_ERR_INTERNAL_ERROR, - "%s", - "Password not yet supported for nbdkit sources"); - return -1; + if (qemuNbdkitCommandPassDataByPipe(cmd, "password", + secret, secretlen) < 0) + return -1; }
- if (proc->source->ncookies > 0) { - /* for now, just report an error rather than passing cookies in - * cleartext on the commandline */ - virReportError(VIR_ERR_INTERNAL_ERROR, - "%s", - "Cookies not yet supported for nbdkit sources"); - return -1; + /* Create a pipe to send the cookies to the nbdkit process. */ + if (proc->source->ncookies) { + char *cookies = + qemuBlockStorageSourceGetCookieString(proc->source);
no need for linebreak after = even when it exceeds line length slightly. Also after changing to doulbe pointer you can add an autofree here too to be consistent.
+ + if (qemuNbdkitCommandPassDataByPipe(cmd, "cookie", + (unsigned char*)cookies, + strlen(cookies)) < 0) + return -1; }
if (proc->source->sslverify == VIR_TRISTATE_BOOL_NO) { diff --git a/src/util/vircommand.c b/src/util/vircommand.c index 014bab9196..838eb6bd16 100644 --- a/src/util/vircommand.c +++ b/src/util/vircommand.c @@ -1703,7 +1703,8 @@ virCommandSetSendBuffer(virCommand *cmd, return -1; }
- if (fcntl(pipefd[1], F_SETFL, O_NONBLOCK) < 0) { + if (!(dryRunBuffer || dryRunCallback) && + fcntl(pipefd[1], F_SETFL, O_NONBLOCK) < 0) { cmd->has_error = errno; VIR_FORCE_CLOSE(pipefd[0]); VIR_FORCE_CLOSE(pipefd[1]);
This belongs to a separate commit with separate justification.
diff --git a/src/util/virutil.h b/src/util/virutil.h index ab8511bf8d..094b399859 100644 --- a/src/util/virutil.h +++ b/src/util/virutil.h @@ -186,7 +186,7 @@ char *virGetPassword(void); * * Returns: -1 on error, 0 on success */ -int virPipe(int fds[2]); +int virPipe(int fds[2]) G_NO_INLINE;
Same here.
/* * virPipeQuiet: /* Some mock implementations for testing */
[...]
+#define PIPE_FD_START 1777 +static int mockpipefd = PIPE_FD_START; +int +virPipeQuiet(int fds[2]) +{ + fds[0] = mockpipefd++; + fds[1] = mockpipefd++; + + if (fcntl(fds[0], F_GETFD) != -1 || + fcntl(fds[1], F_GETFD) != -1) + abort();
You can even create a real pipe and after this check use dup2() to move it to the required FD numbers so that the test output will be stable and you'll have real pipes to work with. In fact I plan to eventually fix all other cases where we use fake FDs to use real FDs moved to stable numbers. In your case it will actually work well because you already account for multiple instances of the object which is not the case elsewhere.
+ + return 0; +} + +static int (*real_close)(int fd); +static void +init_syms(void) +{ + VIR_MOCK_REAL_INIT(close); +} + +int +close(int fd) +{ + int ret;
In other cases we simply try to close() an invalid FD. I'd not bother with mocking this.
+ + init_syms(); + + if (fd >= PIPE_FD_START) + ret = 0; + else + ret = real_close(fd); + + return ret; +}

Signed-off-by: Jonathon Jongsma <jjongsma@redhat.com> --- tests/qemunbdkitdata/disk-network-source-curl.args.1.pipe.1 | 1 + tests/qemunbdkitdata/disk-network-source-curl.args.disk1 | 4 +++- .../disk-network-source-curl.args.disk1.pipe.1780 | 2 +- .../disk-network-source-curl.args.disk1.pipe.1782 | 1 + .../disk-network-source-curl.args.disk1.pipe.49 | 1 + tests/qemunbdkitdata/disk-network-source-curl.args.disk2 | 2 +- .../disk-network-source-curl.args.disk2.pipe.1784 | 1 + .../disk-network-source-curl.args.disk2.pipe.51 | 1 + .../disk-network-source-curl.x86_64-latest.args | 3 ++- tests/qemuxml2argvdata/disk-network-source-curl.xml | 3 +++ 10 files changed, 15 insertions(+), 4 deletions(-) create mode 100644 tests/qemunbdkitdata/disk-network-source-curl.args.1.pipe.1 create mode 100644 tests/qemunbdkitdata/disk-network-source-curl.args.disk1.pipe.1782 create mode 100644 tests/qemunbdkitdata/disk-network-source-curl.args.disk1.pipe.49 create mode 100644 tests/qemunbdkitdata/disk-network-source-curl.args.disk2.pipe.1784 create mode 100644 tests/qemunbdkitdata/disk-network-source-curl.args.disk2.pipe.51 diff --git a/tests/qemunbdkitdata/disk-network-source-curl.args.1.pipe.1 b/tests/qemunbdkitdata/disk-network-source-curl.args.1.pipe.1 new file mode 100644 index 0000000000..20af4ae383 --- /dev/null +++ b/tests/qemunbdkitdata/disk-network-source-curl.args.1.pipe.1 @@ -0,0 +1 @@ +cookie1=cookievalue1; cookie2=cookievalue2 \ No newline at end of file diff --git a/tests/qemunbdkitdata/disk-network-source-curl.args.disk1 b/tests/qemunbdkitdata/disk-network-source-curl.args.disk1 index 13f03c545e..4b6eef8a86 100644 --- a/tests/qemunbdkitdata/disk-network-source-curl.args.disk1 +++ b/tests/qemunbdkitdata/disk-network-source-curl.args.disk1 @@ -4,5 +4,7 @@ nbdkit \ --foreground curl \ protocols=https \ 'url=https://https.example.org:8443/path/to/disk5.iso?foo=bar' \ -cookie=-1779 \ +user=myname \ +password=-1779 \ +cookie=-1781 \ sslverify=false diff --git a/tests/qemunbdkitdata/disk-network-source-curl.args.disk1.pipe.1780 b/tests/qemunbdkitdata/disk-network-source-curl.args.disk1.pipe.1780 index 20af4ae383..ccdd4033fc 100644 --- a/tests/qemunbdkitdata/disk-network-source-curl.args.disk1.pipe.1780 +++ b/tests/qemunbdkitdata/disk-network-source-curl.args.disk1.pipe.1780 @@ -1 +1 @@ -cookie1=cookievalue1; cookie2=cookievalue2 \ No newline at end of file +iscsi-mycluster_myname-secret \ No newline at end of file diff --git a/tests/qemunbdkitdata/disk-network-source-curl.args.disk1.pipe.1782 b/tests/qemunbdkitdata/disk-network-source-curl.args.disk1.pipe.1782 new file mode 100644 index 0000000000..20af4ae383 --- /dev/null +++ b/tests/qemunbdkitdata/disk-network-source-curl.args.disk1.pipe.1782 @@ -0,0 +1 @@ +cookie1=cookievalue1; cookie2=cookievalue2 \ No newline at end of file diff --git a/tests/qemunbdkitdata/disk-network-source-curl.args.disk1.pipe.49 b/tests/qemunbdkitdata/disk-network-source-curl.args.disk1.pipe.49 new file mode 100644 index 0000000000..20af4ae383 --- /dev/null +++ b/tests/qemunbdkitdata/disk-network-source-curl.args.disk1.pipe.49 @@ -0,0 +1 @@ +cookie1=cookievalue1; cookie2=cookievalue2 \ No newline at end of file diff --git a/tests/qemunbdkitdata/disk-network-source-curl.args.disk2 b/tests/qemunbdkitdata/disk-network-source-curl.args.disk2 index 490aea3393..c950eaf6ae 100644 --- a/tests/qemunbdkitdata/disk-network-source-curl.args.disk2 +++ b/tests/qemunbdkitdata/disk-network-source-curl.args.disk2 @@ -5,4 +5,4 @@ nbdkit \ --readonly curl \ protocols=http \ url=http://http.example.org:8080/path/to/disk2.iso \ -cookie=-1781 +cookie=-1783 diff --git a/tests/qemunbdkitdata/disk-network-source-curl.args.disk2.pipe.1784 b/tests/qemunbdkitdata/disk-network-source-curl.args.disk2.pipe.1784 new file mode 100644 index 0000000000..5c035e84c5 --- /dev/null +++ b/tests/qemunbdkitdata/disk-network-source-curl.args.disk2.pipe.1784 @@ -0,0 +1 @@ +cookie1=cookievalue1; cookie2=cookievalue2; cookie3=cookievalue3 \ No newline at end of file diff --git a/tests/qemunbdkitdata/disk-network-source-curl.args.disk2.pipe.51 b/tests/qemunbdkitdata/disk-network-source-curl.args.disk2.pipe.51 new file mode 100644 index 0000000000..5c035e84c5 --- /dev/null +++ b/tests/qemunbdkitdata/disk-network-source-curl.args.disk2.pipe.51 @@ -0,0 +1 @@ +cookie1=cookievalue1; cookie2=cookievalue2; cookie3=cookievalue3 \ No newline at end of file diff --git a/tests/qemuxml2argvdata/disk-network-source-curl.x86_64-latest.args b/tests/qemuxml2argvdata/disk-network-source-curl.x86_64-latest.args index ec6dd13f6c..7f09e84227 100644 --- a/tests/qemuxml2argvdata/disk-network-source-curl.x86_64-latest.args +++ b/tests/qemuxml2argvdata/disk-network-source-curl.x86_64-latest.args @@ -33,9 +33,10 @@ XDG_CONFIG_HOME=/tmp/lib/domain--1-QEMUGuest1/.config \ -blockdev '{"driver":"https","url":"https://https.example.org:8443/path/to/disk1.iso","cookie-secret":"libvirt-5-storage-httpcookie-secret0","node-name":"libvirt-5-storage","auto-read-only":true,"discard":"unmap"}' \ -blockdev '{"node-name":"libvirt-5-format","read-only":true,"driver":"raw","file":"libvirt-5-storage"}' \ -device '{"driver":"virtio-blk-pci","bus":"pci.0","addr":"0x3","drive":"libvirt-5-format","id":"virtio-disk0","bootindex":1}' \ +-object '{"qom-type":"secret","id":"libvirt-4-storage-auth-secret0","data":"9eao5F8qtkGt+seB1HYivWIxbtwUu6MQtg1zpj/oDtUsPr1q8wBYM91uEHCn6j/1","keyid":"masterKey0","iv":"AAECAwQFBgcICQoLDA0ODw==","format":"base64"}' \ -object '{"qom-type":"secret","id":"libvirt-4-format-encryption-secret0","data":"9eao5F8qtkGt+seB1HYivWIxbtwUu6MQtg1zpj/oDtUsPr1q8wBYM91uEHCn6j/1","keyid":"masterKey0","iv":"AAECAwQFBgcICQoLDA0ODw==","format":"base64"}' \ -object '{"qom-type":"secret","id":"libvirt-4-storage-httpcookie-secret0","data":"BUU0KmnWfonHdjzhYhwVQZ5iTI1KweTJ22q8XWUVoBCVu1z70reDuczPBIabZtC3","keyid":"masterKey0","iv":"AAECAwQFBgcICQoLDA0ODw==","format":"base64"}' \ --blockdev '{"driver":"https","url":"https://https.example.org:8443/path/to/disk5.iso?foo=bar","sslverify":false,"cookie-secret":"libvirt-4-storage-httpcookie-secret0","node-name":"libvirt-4-storage","auto-read-only":true,"discard":"unmap"}' \ +-blockdev '{"driver":"https","url":"https://https.example.org:8443/path/to/disk5.iso?foo=bar","username":"myname","password-secret":"libvirt-4-storage-auth-secret0","sslverify":false,"cookie-secret":"libvirt-4-storage-httpcookie-secret0","node-name":"libvirt-4-storage","auto-read-only":true,"discard":"unmap"}' \ -blockdev '{"node-name":"libvirt-4-format","read-only":false,"driver":"luks","key-secret":"libvirt-4-format-encryption-secret0","file":"libvirt-4-storage"}' \ -device '{"driver":"virtio-blk-pci","bus":"pci.0","addr":"0x4","drive":"libvirt-4-format","id":"virtio-disk4"}' \ -object '{"qom-type":"secret","id":"libvirt-3-storage-httpcookie-secret0","data":"BUU0KmnWfonHdjzhYhwVQZ5iTI1KweTJ22q8XWUVoBBv7TuTgTkyAyOPpC2P5qLbOIypLoHpppjz+u5O+X8oT+jA1m7q/OJQ8dk2EFD5c0A=","keyid":"masterKey0","iv":"AAECAwQFBgcICQoLDA0ODw==","format":"base64"}' \ diff --git a/tests/qemuxml2argvdata/disk-network-source-curl.xml b/tests/qemuxml2argvdata/disk-network-source-curl.xml index 1e50314abe..8c3982cd73 100644 --- a/tests/qemuxml2argvdata/disk-network-source-curl.xml +++ b/tests/qemuxml2argvdata/disk-network-source-curl.xml @@ -59,6 +59,9 @@ <encryption format='luks'> <secret type='passphrase' uuid='1148b693-0843-4cef-9f97-8feb4e1ae365'/> </encryption> + <auth username='myname'> + <secret type='iscsi' usage='mycluster_myname'/> + </auth> </source> <target dev='vde' bus='virtio'/> </disk> -- 2.37.3

On Thu, Oct 20, 2022 at 16:59:08 -0500, Jonathon Jongsma wrote:
Signed-off-by: Jonathon Jongsma <jjongsma@redhat.com> --- tests/qemunbdkitdata/disk-network-source-curl.args.1.pipe.1 | 1 + tests/qemunbdkitdata/disk-network-source-curl.args.disk1 | 4 +++- .../disk-network-source-curl.args.disk1.pipe.1780 | 2 +- .../disk-network-source-curl.args.disk1.pipe.1782 | 1 + .../disk-network-source-curl.args.disk1.pipe.49 | 1 + tests/qemunbdkitdata/disk-network-source-curl.args.disk2 | 2 +- .../disk-network-source-curl.args.disk2.pipe.1784 | 1 + .../disk-network-source-curl.args.disk2.pipe.51 | 1 + .../disk-network-source-curl.x86_64-latest.args | 3 ++- tests/qemuxml2argvdata/disk-network-source-curl.xml | 3 +++ 10 files changed, 15 insertions(+), 4 deletions(-) create mode 100644 tests/qemunbdkitdata/disk-network-source-curl.args.1.pipe.1 create mode 100644 tests/qemunbdkitdata/disk-network-source-curl.args.disk1.pipe.1782 create mode 100644 tests/qemunbdkitdata/disk-network-source-curl.args.disk1.pipe.49 create mode 100644 tests/qemunbdkitdata/disk-network-source-curl.args.disk2.pipe.1784 create mode 100644 tests/qemunbdkitdata/disk-network-source-curl.args.disk2.pipe.51
Reviewed-by: Peter Krempa <pkrempa@redhat.com>

Adds the ability to monitor the nbdkit process so that we can take action in case the child exits unexpectedly. When the nbdkit process exits, we pause the vm, restart nbdkit, and then resume the vm. This allows the vm to continue working in the event of a nbdkit failure. Eventually we may want to generalize this functionality since we may need something similar for e.g. qemu-storage-daemon, etc. The process is monitored with the pidfd_open() syscall if it exists (since linux 5.3). Otherwise it resorts to checking whether the process is alive once a second. The one-second time period was chosen somewhat arbitrarily. Signed-off-by: Jonathon Jongsma <jjongsma@redhat.com> --- meson.build | 3 + src/qemu/qemu_nbdkit.c | 220 ++++++++++++++++++++++++++++++++++++++++ src/qemu/qemu_nbdkit.h | 10 ++ src/qemu/qemu_process.c | 13 +++ 4 files changed, 246 insertions(+) diff --git a/meson.build b/meson.build index e4581e74dd..b4ed170ca1 100644 --- a/meson.build +++ b/meson.build @@ -686,6 +686,9 @@ if host_machine.system() == 'linux' # Check if we have new enough kernel to support BPF devices for cgroups v2 [ 'linux/bpf.h', 'BPF_PROG_QUERY' ], [ 'linux/bpf.h', 'BPF_CGROUP_DEVICE' ], + + # process management + [ 'sys/syscall.h', 'SYS_pidfd_open' ], ] endif diff --git a/src/qemu/qemu_nbdkit.c b/src/qemu/qemu_nbdkit.c index 0a0dc5d2a4..f17fe022ec 100644 --- a/src/qemu/qemu_nbdkit.c +++ b/src/qemu/qemu_nbdkit.c @@ -21,9 +21,11 @@ #include <config.h> #include <glib.h> +#include <sys/syscall.h> #include "vircommand.h" #include "virerror.h" +#include "virevent.h" #include "virlog.h" #include "virpidfile.h" #include "virtime.h" @@ -36,6 +38,7 @@ #include "qemu_nbdkit.h" #define LIBVIRT_QEMU_NBDKITPRIV_H_ALLOW #include "qemu_nbdkitpriv.h" +#include "qemu_process.h" #include "qemu_security.h" #include <fcntl.h> @@ -72,6 +75,13 @@ struct _qemuNbdkitCaps { G_DEFINE_TYPE(qemuNbdkitCaps, qemu_nbdkit_caps, G_TYPE_OBJECT); +struct _qemuNbdkitProcessPrivate { + int monitor; + virQEMUDriver *driver; + virDomainObj *vm; +}; + + enum { PIPE_FD_READ = 0, PIPE_FD_WRITE = 1 @@ -588,6 +598,168 @@ qemuNbdkitCapsCacheNew(const char *cachedir) } +static int +qemuNbdkitProcessStartMonitor(qemuNbdkitProcess *proc, + virDomainObj *vm, + virQEMUDriver *driver); + + +static void +qemuNbdkitProcessHandleExit(qemuNbdkitProcess *proc) +{ + qemuNbdkitProcessPrivate *priv = proc->priv; + bool was_running = false; + + VIR_DEBUG("nbdkit process %i died", proc->pid); + + /* clean up resources associated with process */ + qemuNbdkitProcessStop(proc); + + if (!(priv->vm && priv->driver)) { + VIR_WARN("Unable to restart nbdkit -- vm and driver not set"); + return; + } + + VIR_DEBUG("restarting nbdkit process"); + + virObjectLock(priv->vm); + if (virDomainObjBeginJob(priv->vm, VIR_JOB_SUSPEND) < 0) { + VIR_WARN("can't begin job"); + goto cleanup; + } + + /* Pause domain */ + if (virDomainObjGetState(priv->vm, NULL) == VIR_DOMAIN_RUNNING) { + was_running = true; + if (qemuProcessStopCPUs(priv->driver, priv->vm, + VIR_DOMAIN_PAUSED_IOERROR, + VIR_ASYNC_JOB_NONE) < 0) + goto endjob; + VIR_DEBUG("Paused vm while we restart nbdkit backend"); + } + + if (qemuNbdkitProcessStart(proc, priv->vm, priv->driver) < 0) + VIR_WARN("Unable to restart nbkdit process"); + + if (was_running && virDomainObjIsActive(priv->vm)) { + if (qemuProcessStartCPUs(priv->driver, priv->vm, + VIR_DOMAIN_RUNNING_UNPAUSED, + VIR_ASYNC_JOB_NONE) < 0) { + VIR_WARN("Unable to resume guest CPUs after nbdkit restart"); + goto endjob; + } + VIR_DEBUG("Resumed vm"); + } + qemuNbdkitProcessStartMonitor(proc, NULL, NULL); + + endjob: + virDomainObjEndJob(priv->vm); + cleanup: + virObjectUnlock(priv->vm); +} + + +#if WITH_DECL_SYS_PIDFD_OPEN +static void +qemuNbdkitProcessPidfdCb(int watch G_GNUC_UNUSED, + int fd, + int events G_GNUC_UNUSED, + void *opaque) +{ + qemuNbdkitProcess *proc = opaque; + + VIR_FORCE_CLOSE(fd); + qemuNbdkitProcessHandleExit(proc); +} +#else +static void +qemuNbdkitProcessTimeoutCb(int timer G_GNUC_UNUSED, + void *opaque) +{ + qemuNbdkitProcess *proc = opaque; + + if (virProcessKill(proc->pid, 0) < 0) + qemuNbdkitProcessHandleExit(proc); +} +#endif /* WITH_DECL_SYS_PIDFD_OPEN */ + + +static int +qemuNbdkitProcessStartMonitor(qemuNbdkitProcess *proc, + virDomainObj *vm, + virQEMUDriver *driver) +{ + qemuNbdkitProcessPrivate *priv = proc->priv; +#if WITH_DECL_SYS_PIDFD_OPEN + int pidfd; +#endif + + if (vm) { + virObjectRef(vm); + + if (priv->vm) + virObjectUnref(priv->vm); + + priv->vm = vm; + } + + if (driver) + priv->driver = driver; + + if (!(priv->vm && priv->driver)) { + VIR_WARN("set vm and driver before calling %s", G_STRFUNC); + return -1; + } + +#if WITH_DECL_SYS_PIDFD_OPEN + pidfd = syscall(SYS_pidfd_open, proc->pid, 0); + if (pidfd < 0) + return -1; + + priv->monitor = virEventAddHandle(pidfd, + VIR_EVENT_HANDLE_READABLE, + qemuNbdkitProcessPidfdCb, + proc, NULL); +#else + /* fall back to checking once a second */ + priv->monitor = virEventAddTimeout(1000, + qemuNbdkitProcessTimeoutCb, + proc, NULL); +#endif /* WITH_DECL_SYS_PIDFD_OPEN */ + + if (priv->monitor < 0) + return -1; + + VIR_DEBUG("Monitoring nbdkit process %i for exit", proc->pid); + + return 0; +} + + +static void +qemuNbdkitProcessStopMonitor(qemuNbdkitProcess *proc) +{ + qemuNbdkitProcessPrivate *priv = proc->priv; + + if (priv->monitor > 0) { +#if WITH_DECL_SYS_PIDFD_OPEN + virEventRemoveHandle(priv->monitor); +#else + virEventRemoveTimeout(priv->monitor); +#endif /* WITH_DECL_SYS_PIDFD_OPEN */ + priv->monitor = 0; + } +} + + +static void +qemuNbdkitProcessPrivateFree(qemuNbdkitProcessPrivate *priv) +{ + virObjectUnref(priv->vm); + g_free(priv); +} + + static qemuNbdkitProcess * qemuNbdkitProcessNew(virStorageSource *source, const char *pidfile, @@ -601,6 +773,7 @@ qemuNbdkitProcessNew(virStorageSource *source, nbdkit->pid = -1; nbdkit->pidfile = g_strdup(pidfile); nbdkit->socketfile = g_strdup(socketfile); + nbdkit->priv = g_new0(qemuNbdkitProcessPrivate, 1); return nbdkit; } @@ -627,6 +800,45 @@ qemuNbdkitProcessLoad(virStorageSource *source, } +static int +qemuNbdkitStorageSourceManageProcessOne(virStorageSource *src, + virDomainObj *vm, + virQEMUDriver *driver) +{ + qemuDomainStorageSourcePrivate *srcPriv = QEMU_DOMAIN_STORAGE_SOURCE_PRIVATE(src); + qemuNbdkitProcess *nbdkit; + + if (!srcPriv) + return 0; + + nbdkit = srcPriv->nbdkitProcess; + if (nbdkit) { + nbdkit->caps = qemuGetNbdkitCaps(nbdkit->priv->driver); + + if (qemuNbdkitProcessStartMonitor(nbdkit, vm, driver) < 0) + return -1; + } + + return 0; +} + + +int +qemuNbdkitStorageSourceManageProcess(virQEMUDriver *driver, + virDomainObj *vm, + virStorageSource *src) +{ + virStorageSource *backing; + + for (backing = src->backingStore; backing != NULL; backing = backing->backingStore) { + if (qemuNbdkitStorageSourceManageProcessOne(backing, vm, driver) < 0) + return -1; + } + + return qemuNbdkitStorageSourceManageProcessOne(src, vm, driver); +} + + bool qemuNbdkitInitStorageSource(qemuNbdkitCaps *caps, virStorageSource *source, @@ -915,9 +1127,12 @@ qemuNbdkitProcessBuildCommand(qemuNbdkitProcess *proc) void qemuNbdkitProcessFree(qemuNbdkitProcess *proc) { + qemuNbdkitProcessStopMonitor(proc); + g_clear_pointer(&proc->pidfile, g_free); g_clear_pointer(&proc->socketfile, g_free); g_clear_object(&proc->caps); + g_clear_pointer(&proc->priv, qemuNbdkitProcessPrivateFree); g_free(proc); } @@ -988,6 +1203,9 @@ qemuNbdkitProcessStart(qemuNbdkitProcess *proc, goto error; } + if (qemuNbdkitProcessStartMonitor(proc, vm, driver) < 0) + goto error; + return 0; error: @@ -1007,6 +1225,8 @@ qemuNbdkitProcessStop(qemuNbdkitProcess *proc) { int ret; + qemuNbdkitProcessStopMonitor(proc); + if (proc->pid < 0) return 0; diff --git a/src/qemu/qemu_nbdkit.h b/src/qemu/qemu_nbdkit.h index c9af6efcfa..da53138d13 100644 --- a/src/qemu/qemu_nbdkit.h +++ b/src/qemu/qemu_nbdkit.h @@ -65,6 +65,11 @@ qemuNbdkitStartStorageSource(virQEMUDriver *driver, void qemuNbdkitStopStorageSource(virStorageSource *src); +int +qemuNbdkitStorageSourceManageProcess(virQEMUDriver *driver, + virDomainObj *vm, + virStorageSource *src); + bool qemuNbdkitCapsGet(qemuNbdkitCaps *nbdkitCaps, qemuNbdkitCapsFlags flag); @@ -76,6 +81,8 @@ qemuNbdkitCapsSet(qemuNbdkitCaps *nbdkitCaps, #define QEMU_TYPE_NBDKIT_CAPS qemu_nbdkit_caps_get_type() G_DECLARE_FINAL_TYPE(qemuNbdkitCaps, qemu_nbdkit_caps, QEMU, NBDKIT_CAPS, GObject); +typedef struct _qemuNbdkitProcessPrivate qemuNbdkitProcessPrivate; + struct _qemuNbdkitProcess { qemuNbdkitCaps *caps; virStorageSource *source; @@ -85,6 +92,8 @@ struct _qemuNbdkitProcess { uid_t user; gid_t group; pid_t pid; + + qemuNbdkitProcessPrivate *priv; }; int @@ -107,4 +116,5 @@ qemuNbdkitProcessLoad(virStorageSource *source, const char *pidfile, const char *socketfile); + G_DEFINE_AUTOPTR_CLEANUP_FUNC(qemuNbdkitProcess, qemuNbdkitProcessFree); diff --git a/src/qemu/qemu_process.c b/src/qemu/qemu_process.c index f405326312..43e828d42f 100644 --- a/src/qemu/qemu_process.c +++ b/src/qemu/qemu_process.c @@ -9009,6 +9009,19 @@ qemuProcessReconnect(void *opaque) } } + for (i = 0; i < obj->def->ndisks; i++) { + virDomainDiskDef *disk = obj->def->disks[i]; + if (qemuNbdkitStorageSourceManageProcess(driver, obj, disk->src) < 0) + goto error; + } + + if (obj->def->os.loader && obj->def->os.loader->nvram) { + if (qemuNbdkitStorageSourceManageProcess(driver, obj, + obj->def->os.loader->nvram) < 0) + goto error; + } + + /* update domain state XML with possibly updated state in virDomainObj */ if (virDomainObjSave(obj, driver->xmlopt, cfg->stateDir) < 0) goto error; -- 2.37.3

On Thu, Oct 20, 2022 at 16:59:09 -0500, Jonathon Jongsma wrote:
Adds the ability to monitor the nbdkit process so that we can take action in case the child exits unexpectedly.
When the nbdkit process exits, we pause the vm, restart nbdkit, and then resume the vm. This allows the vm to continue working in the event of a nbdkit failure.
Eventually we may want to generalize this functionality since we may need something similar for e.g. qemu-storage-daemon, etc.
The process is monitored with the pidfd_open() syscall if it exists (since linux 5.3). Otherwise it resorts to checking whether the process is alive once a second. The one-second time period was chosen somewhat arbitrarily.
Signed-off-by: Jonathon Jongsma <jjongsma@redhat.com> --- meson.build | 3 + src/qemu/qemu_nbdkit.c | 220 ++++++++++++++++++++++++++++++++++++++++ src/qemu/qemu_nbdkit.h | 10 ++ src/qemu/qemu_process.c | 13 +++ 4 files changed, 246 insertions(+)
diff --git a/meson.build b/meson.build index e4581e74dd..b4ed170ca1 100644 --- a/meson.build +++ b/meson.build @@ -686,6 +686,9 @@ if host_machine.system() == 'linux' # Check if we have new enough kernel to support BPF devices for cgroups v2 [ 'linux/bpf.h', 'BPF_PROG_QUERY' ], [ 'linux/bpf.h', 'BPF_CGROUP_DEVICE' ], + + # process management + [ 'sys/syscall.h', 'SYS_pidfd_open' ], ] endif
diff --git a/src/qemu/qemu_nbdkit.c b/src/qemu/qemu_nbdkit.c index 0a0dc5d2a4..f17fe022ec 100644 --- a/src/qemu/qemu_nbdkit.c +++ b/src/qemu/qemu_nbdkit.c @@ -21,9 +21,11 @@
#include <config.h> #include <glib.h> +#include <sys/syscall.h>
#include "vircommand.h" #include "virerror.h" +#include "virevent.h" #include "virlog.h" #include "virpidfile.h" #include "virtime.h" @@ -36,6 +38,7 @@ #include "qemu_nbdkit.h" #define LIBVIRT_QEMU_NBDKITPRIV_H_ALLOW #include "qemu_nbdkitpriv.h" +#include "qemu_process.h" #include "qemu_security.h"
#include <fcntl.h> @@ -72,6 +75,13 @@ struct _qemuNbdkitCaps { G_DEFINE_TYPE(qemuNbdkitCaps, qemu_nbdkit_caps, G_TYPE_OBJECT);
+struct _qemuNbdkitProcessPrivate { + int monitor; + virQEMUDriver *driver;
Note that 'driver' can be fetched from vm's privateData's 'driver' field so you don't need to have both.
+ virDomainObj *vm; +}; + + enum { PIPE_FD_READ = 0, PIPE_FD_WRITE = 1 @@ -588,6 +598,168 @@ qemuNbdkitCapsCacheNew(const char *cachedir) }
+static int +qemuNbdkitProcessStartMonitor(qemuNbdkitProcess *proc, + virDomainObj *vm, + virQEMUDriver *driver);
As above. 'driver' is redundant.
+ + +static void +qemuNbdkitProcessHandleExit(qemuNbdkitProcess *proc) +{ + qemuNbdkitProcessPrivate *priv = proc->priv; + bool was_running = false; + + VIR_DEBUG("nbdkit process %i died", proc->pid); + + /* clean up resources associated with process */ + qemuNbdkitProcessStop(proc); + + if (!(priv->vm && priv->driver)) { + VIR_WARN("Unable to restart nbdkit -- vm and driver not set");
The function that registers this handler ensures that the required stuff is set so this is dead code. And if not VIR_WARN is not the appropriate action.
+ return; + } + + VIR_DEBUG("restarting nbdkit process"); + + virObjectLock(priv->vm); + if (virDomainObjBeginJob(priv->vm, VIR_JOB_SUSPEND) < 0) { + VIR_WARN("can't begin job");
Same here, it's an error.
+ goto cleanup; + } + + /* Pause domain */ + if (virDomainObjGetState(priv->vm, NULL) == VIR_DOMAIN_RUNNING) { + was_running = true; + if (qemuProcessStopCPUs(priv->driver, priv->vm, + VIR_DOMAIN_PAUSED_IOERROR, + VIR_ASYNC_JOB_NONE) < 0) + goto endjob; + VIR_DEBUG("Paused vm while we restart nbdkit backend");
I don't think we should pause the VM here, it's not like the start of NBDkit is slow. Additionally after testing this, it doesn't actually even prevent the VM from getting an I/O error after nbdkit is started back. Just a subsequent request re-establishes the connection. Please drop all of the VM state modification here.
+ } + + if (qemuNbdkitProcessStart(proc, priv->vm, priv->driver) < 0) + VIR_WARN("Unable to restart nbkdit process"); + + if (was_running && virDomainObjIsActive(priv->vm)) { + if (qemuProcessStartCPUs(priv->driver, priv->vm, + VIR_DOMAIN_RUNNING_UNPAUSED, + VIR_ASYNC_JOB_NONE) < 0) { + VIR_WARN("Unable to resume guest CPUs after nbdkit restart"); + goto endjob; + } + VIR_DEBUG("Resumed vm"); + } + qemuNbdkitProcessStartMonitor(proc, NULL, NULL); + + endjob: + virDomainObjEndJob(priv->vm); + cleanup: + virObjectUnlock(priv->vm); +} + + +#if WITH_DECL_SYS_PIDFD_OPEN +static void +qemuNbdkitProcessPidfdCb(int watch G_GNUC_UNUSED, + int fd, + int events G_GNUC_UNUSED, + void *opaque) +{ + qemuNbdkitProcess *proc = opaque; + + VIR_FORCE_CLOSE(fd); + qemuNbdkitProcessHandleExit(proc); +} +#else +static void +qemuNbdkitProcessTimeoutCb(int timer G_GNUC_UNUSED, + void *opaque) +{ + qemuNbdkitProcess *proc = opaque; + + if (virProcessKill(proc->pid, 0) < 0) + qemuNbdkitProcessHandleExit(proc); +} +#endif /* WITH_DECL_SYS_PIDFD_OPEN */ + + +static int +qemuNbdkitProcessStartMonitor(qemuNbdkitProcess *proc, + virDomainObj *vm, + virQEMUDriver *driver) +{ + qemuNbdkitProcessPrivate *priv = proc->priv; +#if WITH_DECL_SYS_PIDFD_OPEN + int pidfd; +#endif + + if (vm) { + virObjectRef(vm); + + if (priv->vm) + virObjectUnref(priv->vm); + + priv->vm = vm; + } + + if (driver) + priv->driver = driver; + + if (!(priv->vm && priv->driver)) { + VIR_WARN("set vm and driver before calling %s", G_STRFUNC);
Report proper error here. That also captures the function name.
+ return -1; + } + +#if WITH_DECL_SYS_PIDFD_OPEN + pidfd = syscall(SYS_pidfd_open, proc->pid, 0); + if (pidfd < 0) + return -1; + + priv->monitor = virEventAddHandle(pidfd, + VIR_EVENT_HANDLE_READABLE, + qemuNbdkitProcessPidfdCb, + proc, NULL); +#else + /* fall back to checking once a second */ + priv->monitor = virEventAddTimeout(1000, + qemuNbdkitProcessTimeoutCb, + proc, NULL); +#endif /* WITH_DECL_SYS_PIDFD_OPEN */ + + if (priv->monitor < 0) + return -1; + + VIR_DEBUG("Monitoring nbdkit process %i for exit", proc->pid); + + return 0; +} + + +static void +qemuNbdkitProcessStopMonitor(qemuNbdkitProcess *proc) +{ + qemuNbdkitProcessPrivate *priv = proc->priv; + + if (priv->monitor > 0) { +#if WITH_DECL_SYS_PIDFD_OPEN + virEventRemoveHandle(priv->monitor); +#else + virEventRemoveTimeout(priv->monitor); +#endif /* WITH_DECL_SYS_PIDFD_OPEN */ + priv->monitor = 0; + } +} + + +static void +qemuNbdkitProcessPrivateFree(qemuNbdkitProcessPrivate *priv) +{ + virObjectUnref(priv->vm); + g_free(priv); +} + + static qemuNbdkitProcess * qemuNbdkitProcessNew(virStorageSource *source, const char *pidfile, @@ -601,6 +773,7 @@ qemuNbdkitProcessNew(virStorageSource *source, nbdkit->pid = -1; nbdkit->pidfile = g_strdup(pidfile); nbdkit->socketfile = g_strdup(socketfile); + nbdkit->priv = g_new0(qemuNbdkitProcessPrivate, 1);
return nbdkit; } @@ -627,6 +800,45 @@ qemuNbdkitProcessLoad(virStorageSource *source, }
+static int +qemuNbdkitStorageSourceManageProcessOne(virStorageSource *src, + virDomainObj *vm, + virQEMUDriver *driver) +{ + qemuDomainStorageSourcePrivate *srcPriv = QEMU_DOMAIN_STORAGE_SOURCE_PRIVATE(src); + qemuNbdkitProcess *nbdkit; + + if (!srcPriv) + return 0; + + nbdkit = srcPriv->nbdkitProcess; + if (nbdkit) { + nbdkit->caps = qemuGetNbdkitCaps(nbdkit->priv->driver);
On standard VM startup this will leak the existing reference assigned via qemuDomainPrepareStorageSourceNbdkit.
+ + if (qemuNbdkitProcessStartMonitor(nbdkit, vm, driver) < 0) + return -1; + } + + return 0; +}
participants (5)
-
Jonathon Jongsma
-
Ján Tomko
-
Laine Stump
-
Peter Krempa
-
Richard W.M. Jones