--- include/libvirt/virterror.h | 1 + po/POTFILES | 1 + src/qemu/meson.build | 1 + src/qemu/qemu_block.c | 64 +- src/qemu/qemu_block.h | 1 + src/qemu/qemu_command.c | 26 +- src/qemu/qemu_conf.c | 19 + src/qemu/qemu_conf.h | 5 + src/qemu/qemu_domain.c | 110 ++- src/qemu/qemu_domain.h | 5 + src/qemu/qemu_driver.c | 4 +- src/qemu/qemu_extdevice.c | 25 + src/qemu/qemu_nbdkit.c | 629 ++++++++++++++++++ src/qemu/qemu_nbdkit.h | 89 +++ src/qemu/qemu_validate.c | 22 +- src/qemu/qemu_validate.h | 4 +- src/util/virerror.c | 1 + tests/qemublocktest.c | 8 +- tests/qemustatusxml2xmldata/modern-in.xml | 1 - ...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 + ...work-source-curl-nbdkit.x86_64-latest.args | 49 ++ .../disk-network-source-curl-nbdkit.xml | 1 + ...isk-network-source-curl.x86_64-latest.args | 53 ++ .../disk-network-source-curl.xml | 71 ++ tests/qemuxml2argvtest.c | 12 + tests/testutilsqemu.c | 16 + tests/testutilsqemu.h | 4 + 30 files changed, 1278 insertions(+), 33 deletions(-) create mode 100644 src/qemu/qemu_nbdkit.c create mode 100644 src/qemu/qemu_nbdkit.h 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.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 diff --git a/include/libvirt/virterror.h b/include/libvirt/virterror.h index df13e4f11e..dd198dfd7d 100644 --- a/include/libvirt/virterror.h +++ b/include/libvirt/virterror.h @@ -141,6 +141,7 @@ typedef enum { VIR_FROM_TPM = 70, /* Error from TPM (Since: 5.6.0) */ VIR_FROM_BPF = 71, /* Error from BPF code (Since: 5.10.0) */ VIR_FROM_CH = 72, /* Error from Cloud-Hypervisor driver (Since: 7.5.0) */ + VIR_FROM_NBDKIT = 73, /* Error from Nbdkit code (Since: 8.5.0) */ # ifdef VIR_ENUM_SENTINELS VIR_ERR_DOMAIN_LAST /* (Since: 0.9.13) */ diff --git a/po/POTFILES b/po/POTFILES index faaba53c8f..99284b8173 100644 --- a/po/POTFILES +++ b/po/POTFILES @@ -177,6 +177,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_block.c b/src/qemu/qemu_block.c index 9fe22f18f2..91f17f133f 100644 --- a/src/qemu/qemu_block.c +++ b/src/qemu/qemu_block.c @@ -773,6 +773,35 @@ 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 = { NULL }; + + /* srcPriv->nbdkitProcess will already be initialized if we can use nbdkit + * to proxy this storage source */ + if (!(srcPriv && srcPriv->nbdkitProcess)) + return NULL; + + host.transport = VIR_STORAGE_NET_HOST_TRANS_UNIX; + host.socket = srcPriv->nbdkitProcess->socketfile; + serverprops = qemuBlockStorageSourceBuildJSONSocketAddress(&host, + false); + if (!serverprops) + return NULL; + + if (virJSONValueObjectAdd(&ret, + "a:server", &serverprops, + NULL) < 0) + return NULL; + + return ret; +} + + static virJSONValue * qemuBlockStorageSourceGetISCSIProps(virStorageSource *src, bool onlytarget) @@ -1207,6 +1236,14 @@ qemuBlockStorageSourceGetBackendProps(virStorageSource *src, case VIR_STORAGE_NET_PROTOCOL_FTP: case VIR_STORAGE_NET_PROTOCOL_FTPS: case VIR_STORAGE_NET_PROTOCOL_TFTP: + /* first try to use nbdkit for http/ftp sources */ + if ((fileprops = qemuBlockStorageSourceGetNbdkitProps(src))) { + driver = "nbd"; + break; + } + + /* nbdkit is not supported for this host/source, fall back to old + * qemu storage plugins */ driver = virStorageNetProtocolTypeToString(src->protocol); if (!(fileprops = qemuBlockStorageSourceGetCURLProps(src, onlytarget))) return NULL; @@ -1237,6 +1274,14 @@ qemuBlockStorageSourceGetBackendProps(virStorageSource *src, break; case VIR_STORAGE_NET_PROTOCOL_SSH: + /* first try to use nbdkit for ssh sources */ + if ((fileprops = qemuBlockStorageSourceGetNbdkitProps(src))) { + driver = "nbd"; + break; + } + + /* nbdkit is not supported for this host/source. fallback to + * the old qemu storage plugins */ driver = "ssh"; if (!(fileprops = qemuBlockStorageSourceGetSshProps(src))) return NULL; @@ -1671,6 +1716,7 @@ qemuBlockStorageSourceAttachPrepareBlockdev(virStorageSource *src, { g_autoptr(qemuBlockStorageSourceAttachData) data = NULL; unsigned int backendpropsflags = 0; + qemuDomainStorageSourcePrivate *srcpriv = QEMU_DOMAIN_STORAGE_SOURCE_PRIVATE(src); if (autoreadonly) backendpropsflags |= QEMU_BLOCK_STORAGE_SOURCE_BACKEND_PROPS_AUTO_READONLY; @@ -1685,6 +1731,7 @@ qemuBlockStorageSourceAttachPrepareBlockdev(virStorageSource *src, data->storageNodeName = src->nodestorage; data->formatNodeName = src->nodeformat; + data->useNbdkit = srcpriv && srcpriv->nbdkitProcess; if (qemuBlockStorageSourceNeedsStorageSliceLayer(src)) { if (!(data->storageSliceProps = qemuBlockStorageSourceGetBlockdevStorageSliceProps(src))) @@ -1701,6 +1748,10 @@ static int qemuBlockStorageSourceAttachApplyStorageDeps(qemuMonitor *mon, qemuBlockStorageSourceAttachData *data) { + /* when using nbdkit, data is not passed via qemu secrets */ + if (data->useNbdkit) + return 0; + if (data->prmgrProps && qemuMonitorAddObject(mon, &data->prmgrProps, &data->prmgrAlias) < 0) return -1; @@ -2205,6 +2256,8 @@ qemuBlockGetBackingStoreString(virStorageSource *src, virJSONValue *props = NULL; g_autoptr(virURI) uri = NULL; g_autofree char *backingJSON = NULL; + qemuDomainStorageSourcePrivate *srcPriv = QEMU_DOMAIN_STORAGE_SOURCE_PRIVATE(src); + bool useNbdkit = srcPriv && srcPriv->nbdkitProcess; if (!src->sliceStorage) { if (virStorageSourceIsLocalStorage(src)) { @@ -2223,7 +2276,8 @@ qemuBlockGetBackingStoreString(virStorageSource *src, src->ncookies == 0 && src->sslverify == VIR_TRISTATE_BOOL_ABSENT && src->timeout == 0 && - src->readahead == 0) { + src->readahead == 0 && + !useNbdkit) { switch ((virStorageNetProtocol) src->protocol) { case VIR_STORAGE_NET_PROTOCOL_NBD: @@ -2602,6 +2656,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: @@ -2630,6 +2685,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_block.h b/src/qemu/qemu_block.h index 8641c8a2d2..a1f98f4452 100644 --- a/src/qemu/qemu_block.h +++ b/src/qemu/qemu_block.h @@ -113,6 +113,7 @@ struct qemuBlockStorageSourceAttachData { char *tlsAlias; virJSONValue *tlsKeySecretProps; char *tlsKeySecretAlias; + bool useNbdkit; }; diff --git a/src/qemu/qemu_command.c b/src/qemu/qemu_command.c index b307d3139c..dd410362fb 100644 --- a/src/qemu/qemu_command.c +++ b/src/qemu/qemu_command.c @@ -1578,6 +1578,7 @@ qemuBuildNetworkDriveStr(virStorageSource *src, qemuDomainSecretInfo *secinfo) { g_auto(virBuffer) buf = VIR_BUFFER_INITIALIZER; + qemuDomainStorageSourcePrivate *priv = QEMU_DOMAIN_STORAGE_SOURCE_PRIVATE(src); size_t i; char *ret = NULL; @@ -1637,6 +1638,13 @@ qemuBuildNetworkDriveStr(virStorageSource *src, case VIR_STORAGE_NET_PROTOCOL_FTP: case VIR_STORAGE_NET_PROTOCOL_FTPS: case VIR_STORAGE_NET_PROTOCOL_TFTP: + if (priv && priv->nbdkitProcess) { + virBufferAsprintf(&buf, "nbd:unix:%s", priv->nbdkitProcess->socketfile); + ret = virBufferContentAndReset(&buf); + } else { + ret = qemuBuildNetworkDriveURI(src); + } + break; case VIR_STORAGE_NET_PROTOCOL_ISCSI: case VIR_STORAGE_NET_PROTOCOL_GLUSTER: ret = qemuBuildNetworkDriveURI(src); @@ -2497,13 +2505,17 @@ qemuBuildBlockStorageSourceAttachDataCommandline(virCommand *cmd, { char *tmp; - if (qemuBuildObjectCommandline(cmd, data->prmgrProps, qemuCaps) < 0 || - qemuBuildObjectCommandline(cmd, data->authsecretProps, qemuCaps) < 0 || - qemuBuildObjectCommandline(cmd, data->encryptsecretProps, qemuCaps) < 0 || - qemuBuildObjectCommandline(cmd, data->httpcookiesecretProps, qemuCaps) < 0 || - qemuBuildObjectCommandline(cmd, data->tlsKeySecretProps, qemuCaps) < 0 || - qemuBuildObjectCommandline(cmd, data->tlsProps, qemuCaps) < 0) - return -1; + /* disks that are backed by nbdkit do not send these secrets to qemu, but + * rather directly to nbdkit */ + if (!data->useNbdkit) { + if (qemuBuildObjectCommandline(cmd, data->prmgrProps, qemuCaps) < 0 || + qemuBuildObjectCommandline(cmd, data->authsecretProps, qemuCaps) < 0 || + qemuBuildObjectCommandline(cmd, data->encryptsecretProps, qemuCaps) < 0 || + qemuBuildObjectCommandline(cmd, data->httpcookiesecretProps, qemuCaps) < 0 || + qemuBuildObjectCommandline(cmd, data->tlsKeySecretProps, qemuCaps) < 0 || + qemuBuildObjectCommandline(cmd, data->tlsProps, qemuCaps) < 0) + return -1; + } if (data->driveCmd) virCommandAddArgList(cmd, "-drive", data->driveCmd, NULL); diff --git a/src/qemu/qemu_conf.c b/src/qemu/qemu_conf.c index 3b75cdeb95..22aeab0c49 100644 --- a/src/qemu/qemu_conf.c +++ b/src/qemu/qemu_conf.c @@ -1571,3 +1571,22 @@ qemuGetMemoryBackingPath(virQEMUDriver *driver, *memPath = g_strdup_printf("%s/%s", domainPath, alias); 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 (!QEMU_IS_NBDKIT_CAPS(driver->nbdkitCaps)) + return NULL; + + return g_object_ref(driver->nbdkitCaps); +} diff --git a/src/qemu/qemu_conf.h b/src/qemu/qemu_conf.h index c40c452f58..f14f9fc4c1 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" @@ -306,6 +307,8 @@ struct _virQEMUDriver { /* Immutable pointer, self-locking APIs */ virHashAtomic *migrationErrors; + + qemuNbdkitCaps *nbdkitCaps; }; virQEMUDriverConfig *virQEMUDriverConfigNew(bool privileged, @@ -359,3 +362,5 @@ int qemuGetMemoryBackingPath(virQEMUDriver *driver, const virDomainDef *def, const char *alias, char **memPath); + +qemuNbdkitCaps * qemuGetNbdkitCaps(virQEMUDriver *driver); diff --git a/src/qemu/qemu_domain.c b/src/qemu/qemu_domain.c index 9769e3bb92..faeb779b3f 100644 --- a/src/qemu/qemu_domain.c +++ b/src/qemu/qemu_domain.c @@ -21,6 +21,7 @@ #include <config.h> +#include "qemu_conf.h" #include "qemu_domain.h" #include "qemu_alias.h" #include "qemu_block.h" @@ -818,6 +819,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); } @@ -1293,10 +1295,7 @@ qemuDomainSecretStorageSourcePrepare(qemuDomainObjPrivate *priv, if (!src->auth && !hasEnc && src->ncookies == 0) return 0; - if (!(src->privateData = qemuDomainStorageSourcePrivateNew())) - return -1; - - srcPriv = QEMU_DOMAIN_STORAGE_SOURCE_PRIVATE(src); + srcPriv = qemuDomainStorageSourcePrivateFetch(src); if (src->auth) { virSecretUsageType usageType = VIR_SECRET_USAGE_TYPE_ISCSI; @@ -1321,7 +1320,9 @@ qemuDomainSecretStorageSourcePrepare(qemuDomainObjPrivate *priv, return -1; } - if (src->ncookies && + /* when using nbdkit for http(s) sources, we don't need to pass cookies as + * qemu secrets */ + if (!srcPriv->nbdkitProcess && src->ncookies && virQEMUCapsGet(priv->qemuCaps, QEMU_CAPS_BLOCKDEV) && !(srcPriv->httpcookie = qemuDomainSecretStorageSourcePrepareCookies(priv, src, @@ -1792,6 +1793,31 @@ 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); + + ctxt->node = node; + + if (!(pidfile = virXPathString("string(./pidfile)", ctxt))) + return -1; + + if (!(socketfile = virXPathString("string(./socketfile)", ctxt))) + return -1; + + if (!srcpriv->nbdkitProcess) + srcpriv->nbdkitProcess = qemuNbdkitProcessLoad(src, pidfile, socketfile); + + return 0; +} + + static int qemuStorageSourcePrivateDataParse(xmlXPathContextPtr ctxt, virStorageSource *src) @@ -1802,6 +1828,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); @@ -1845,6 +1872,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; } @@ -1862,6 +1893,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) @@ -1900,6 +1948,9 @@ qemuStorageSourcePrivateDataFormat(virStorageSource *src, if (src->thresholdEventWithIndex) virBufferAddLit(buf, "<thresholdEvent indexUsed='yes'/>\n"); + if (srcPriv) + qemuStorageSourcePrivateDataFormatNbdkit(srcPriv->nbdkitProcess, buf); + return 0; } @@ -4804,6 +4855,7 @@ qemuDomainValidateActualNetDef(const virDomainNetDef *net, int qemuDomainValidateStorageSource(virStorageSource *src, virQEMUCaps *qemuCaps, + qemuNbdkitCaps *nbdkitCaps, bool maskBlockdev) { virStorageType actualType = virStorageSourceGetActualType(src); @@ -4899,7 +4951,8 @@ qemuDomainValidateStorageSource(virStorageSource *src, return -1; } - if (!src->detected && !blockdev) { + if (!src->detected && !blockdev && + !qemuNbdkitCapsGet(nbdkitCaps, QEMU_NBDKIT_CAPS_PLUGIN_CURL)) { virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s", _("http cookies are not supported by this QEMU binary")); return -1; @@ -4920,7 +4973,8 @@ qemuDomainValidateStorageSource(virStorageSource *src, return -1; } - if (!src->detected && !blockdev) { + if (!src->detected && !blockdev && + !qemuNbdkitCapsGet(nbdkitCaps, QEMU_NBDKIT_CAPS_FILTER_READAHEAD)) { virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s", _("readahead setting is not supported with this QEMU binary")); return -1; @@ -4938,7 +4992,8 @@ qemuDomainValidateStorageSource(virStorageSource *src, return -1; } - if (!src->detected && !blockdev) { + if (!src->detected && !blockdev && + !qemuNbdkitCapsGet(nbdkitCaps, QEMU_NBDKIT_CAPS_PLUGIN_CURL)) { virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s", _("timeout setting is not supported with this QEMU binary")); return -1; @@ -7705,6 +7760,7 @@ qemuDomainDetermineDiskChain(virQEMUDriver *driver, bool isSD = qemuDiskBusIsSD(disk->bus); uid_t uid; gid_t gid; + g_autoptr(qemuNbdkitCaps) nbdkitcaps = qemuGetNbdkitCaps(priv->driver); if (!disksrc) disksrc = disk->src; @@ -7787,7 +7843,8 @@ qemuDomainDetermineDiskChain(virQEMUDriver *driver, n->format = VIR_STORAGE_FILE_RAW; /* mask-out blockdev for 'sd' disks */ - if (qemuDomainValidateStorageSource(n, priv->qemuCaps, isSD) < 0) + if (qemuDomainValidateStorageSource(n, priv->qemuCaps, + nbdkitcaps, isSD) < 0) return -1; qemuDomainPrepareStorageSourceConfig(n, cfg, priv->qemuCaps); @@ -10112,6 +10169,29 @@ 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 + */ +static void +qemuDomainPrepareStorageSourceNbdkit(virDomainDiskDef *disk, + virQEMUDriverConfig *cfg, + qemuDomainObjPrivate *priv) +{ + g_autoptr(qemuNbdkitCaps) nbdkit = qemuGetNbdkitCaps(priv->driver); + if (!nbdkit) + return; + + if (virStorageSourceGetActualType(disk->src) != VIR_STORAGE_TYPE_NETWORK) + return; + + qemuNbdkitInitStorageSource(nbdkit, disk->src, priv->libDir, + disk->info.alias, cfg->user, cfg->group); +} + + /* qemuProcessPrepareStorageSourceTLS: * @source: source for a disk * @cfg: driver configuration @@ -10828,7 +10908,9 @@ qemuDomainPrepareDiskSourceLegacy(virDomainDiskDef *disk, qemuDomainObjPrivate *priv, virQEMUDriverConfig *cfg) { - if (qemuDomainValidateStorageSource(disk->src, priv->qemuCaps, true) < 0) + g_autoptr(qemuNbdkitCaps) nbdkitcaps = qemuGetNbdkitCaps(priv->driver); + if (qemuDomainValidateStorageSource(disk->src, priv->qemuCaps, + nbdkitcaps, true) < 0) return -1; qemuDomainPrepareStorageSourceConfig(disk->src, cfg, priv->qemuCaps); @@ -10846,6 +10928,8 @@ qemuDomainPrepareDiskSourceLegacy(virDomainDiskDef *disk, priv) < 0) return -1; + qemuDomainPrepareStorageSourceNbdkit(disk, cfg, priv); + return 0; } @@ -10857,6 +10941,7 @@ qemuDomainPrepareStorageSourceBlockdevNodename(virDomainDiskDef *disk, qemuDomainObjPrivate *priv, virQEMUDriverConfig *cfg) { + g_autoptr(qemuNbdkitCaps) nbdkitcaps = qemuGetNbdkitCaps(priv->driver); src->nodestorage = g_strdup_printf("%s-storage", nodenameprefix); src->nodeformat = g_strdup_printf("%s-format", nodenameprefix); @@ -10866,7 +10951,8 @@ qemuDomainPrepareStorageSourceBlockdevNodename(virDomainDiskDef *disk, if (src->encryption && src->encryption->engine == VIR_STORAGE_ENCRYPTION_ENGINE_DEFAULT) src->encryption->engine = VIR_STORAGE_ENCRYPTION_ENGINE_QEMU; - if (qemuDomainValidateStorageSource(src, priv->qemuCaps, false) < 0) + if (qemuDomainValidateStorageSource(src, priv->qemuCaps, + nbdkitcaps, false) < 0) return -1; qemuDomainPrepareStorageSourceConfig(src, cfg, priv->qemuCaps); @@ -10887,6 +10973,8 @@ qemuDomainPrepareStorageSourceBlockdevNodename(virDomainDiskDef *disk, if (qemuDomainPrepareStorageSourceNFS(src) < 0) return -1; + qemuDomainPrepareStorageSourceNbdkit(disk, cfg, priv); + return 0; } diff --git a/src/qemu/qemu_domain.h b/src/qemu/qemu_domain.h index a87dfff1bb..34e1bb6fba 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" @@ -290,6 +291,9 @@ struct _qemuDomainStorageSourcePrivate { /* key for decrypting TLS certificate */ qemuDomainSecretInfo *tlsKeySecret; + + /* an nbdkit process for serving network storage sources */ + qemuNbdkitProcess *nbdkitProcess; }; virObject *qemuDomainStorageSourcePrivateNew(void); @@ -995,6 +999,7 @@ qemuDomainPrepareDiskSourceData(virDomainDiskDef *disk, int qemuDomainValidateStorageSource(virStorageSource *src, virQEMUCaps *qemuCaps, + qemuNbdkitCaps *nbdkitCaps, bool maskBlockdev); diff --git a/src/qemu/qemu_driver.c b/src/qemu/qemu_driver.c index 3b5c3db67c..d1a972eb37 100644 --- a/src/qemu/qemu_driver.c +++ b/src/qemu/qemu_driver.c @@ -831,6 +831,9 @@ qemuStateInitialize(bool privileged, defsecmodel))) goto error; + /* find whether nbdkit is available and query its capabilities */ + qemu_driver->nbdkitCaps = qemuNbdkitCapsQuery(); + /* 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. */ @@ -14471,7 +14474,6 @@ qemuDomainBlockPivot(virQEMUDriver *driver, if (reuse && shallow && virQEMUCapsGet(priv->qemuCaps, QEMU_CAPS_BLOCKDEV_SNAPSHOT_ALLOW_WRITE_ONLY) && virStorageSourceHasBacking(disk->mirror)) { - if (!(chainattachdata = qemuBuildStorageSourceChainAttachPrepareBlockdev(disk->mirror->backingStore))) return -1; diff --git a/src/qemu/qemu_extdevice.c b/src/qemu/qemu_extdevice.c index b8e3c1000a..0f9361b294 100644 --- a/src/qemu/qemu_extdevice.c +++ b/src/qemu/qemu_extdevice.c @@ -218,6 +218,14 @@ qemuExtDevicesStart(virQEMUDriver *driver, return -1; } + for (i = 0; i < def->ndisks; i++) { + virDomainDiskDef *disk = def->disks[i]; + qemuDomainStorageSourcePrivate *priv = QEMU_DOMAIN_STORAGE_SOURCE_PRIVATE(disk->src); + if (priv && priv->nbdkitProcess && + qemuNbdkitProcessStart(priv->nbdkitProcess, vm, driver) < 0) + return -1; + } + return 0; } @@ -262,6 +270,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]; + qemuDomainStorageSourcePrivate *priv = QEMU_DOMAIN_STORAGE_SOURCE_PRIVATE(disk->src); + + if (priv && priv->nbdkitProcess) + qemuNbdkitProcessStop(priv->nbdkitProcess); + } } @@ -324,6 +340,15 @@ qemuExtDevicesSetupCgroup(virQEMUDriver *driver, return -1; } + for (i = 0; i < def->ndisks; i++) { + virDomainDiskDef *disk = def->disks[i]; + qemuDomainStorageSourcePrivate *priv = QEMU_DOMAIN_STORAGE_SOURCE_PRIVATE(disk); + + if (priv && priv->nbdkitProcess && + qemuNbdkitProcessSetupCgroup(priv->nbdkitProcess, 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 new file mode 100644 index 0000000000..dd8759689c --- /dev/null +++ b/src/qemu/qemu_nbdkit.c @@ -0,0 +1,629 @@ +/* + * 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 "virsecureerase.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_NBDKIT + +VIR_LOG_INIT("qemu.nbdkit"); + +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 +qemuNbdkitQueryPlugins(qemuNbdkitCaps *nbdkit) +{ + qemuNbdkitQueryPlugin(nbdkit, "curl", QEMU_NBDKIT_CAPS_PLUGIN_CURL); + qemuNbdkitQueryPlugin(nbdkit, "ssh", QEMU_NBDKIT_CAPS_PLUGIN_SSH); +} + + +static void +qemuNbdkitQueryFilters(qemuNbdkitCaps *nbdkit) +{ + qemuNbdkitQueryFilter(nbdkit, "readahead", + QEMU_NBDKIT_CAPS_FILTER_READAHEAD); +} + + +static int +qemuNbdkitQueryVersion(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; +} + + +qemuNbdkitCaps * +qemuNbdkitCapsQuery(void) +{ + qemuNbdkitCaps *caps = NULL; + g_autofree char *path = virFindFileInPath("nbdkit"); + + if (!path) + return NULL; + + // make sure it's executable + if (!virFileIsExecutable(path)) { + virReportError(VIR_ERR_INTERNAL_ERROR, _("nbdkit '%s' is not executable"), + path); + return NULL; + } + + VIR_DEBUG("found nbdkit executable '%s'", path); + caps = qemuNbdkitCapsNew(path); + + qemuNbdkitQueryPlugins(caps); + qemuNbdkitQueryFilters(caps); + qemuNbdkitQueryVersion(caps); + + return caps; +} + + +bool +qemuNbdkitCapsGet(qemuNbdkitCaps *nbdkitCaps, qemuNbdkitCapsFlags flag) +{ + return virBitmapIsBitSet(nbdkitCaps->flags, flag); +} + + +void +qemuNbdkitCapsSet(qemuNbdkitCaps *nbdkitCaps, qemuNbdkitCapsFlags flag) +{ + ignore_value(virBitmapSetBit(nbdkitCaps->flags, flag)); +} + + +static int childProcess(int fd, uint8_t *output, size_t outputlen) +{ + if (safewrite(fd, output, outputlen) < 0) { + virReportSystemError(errno, "%s", + _("failed to write to socket for nbdkit")); + return -errno; + } + return 0; +} + + +/* Forks a process to write a password to a socket to the nbdkit process. + * Returns a file descriptor that can be passed to ndkit */ +static int qemuNbdkitForkHandler(uint8_t *output, size_t outputlen) +{ + enum { + PARENT_SOCKET = 0, + CHILD_SOCKET = 1 + }; + int pair[2] = { -1, -1 }; + pid_t pid; + + if (socketpair(AF_UNIX, SOCK_STREAM, 0, pair) < 0) { + virReportSystemError(errno, "%s", + _("failed to create socket for nbdkit")); + return -errno; + } + + pid = virFork(); + + if (pid < 0) + return -errno; + + /* child process */ + if (pid == 0) { + int ret = childProcess(pair[CHILD_SOCKET], output, outputlen); + _exit(ret); + } + + /* parent process */ + VIR_FORCE_CLOSE(pair[CHILD_SOCKET]); + + return pair[PARENT_SOCKET]; +} + + +/* Forks a process to write cookies to a socket to the nbdkit process. + * Returns a file descriptor that can be passed to ndkit */ +static int qemuNbdkitProcessForkCookieHandler(qemuNbdkitProcess *proc) +{ + g_autofree char *cookies = qemuBlockStorageSourceGetCookieString(proc->source); + + if (!cookies) + return 0; + + if ((proc->cookiefd = qemuNbdkitForkHandler((uint8_t*)cookies, strlen(cookies))) < 0) + return -1; + + return 0; +} + + +/* Forks a process to write a password to a socket to the nbdkit process. + * Returns a file descriptor that can be passed to ndkit */ +static int qemuNbdkitProcessForkPasswordHandler(qemuNbdkitProcess *proc) +{ + g_autofree uint8_t *password = NULL; + size_t passwordlen = 0; + g_autoptr(virConnect) conn = virGetConnectSecret(); + + if (!proc->source->auth->username) + return 0; + + if (virSecretGetSecretString(conn, + &proc->source->auth->seclookupdef, + /* FIXME: for some reason auth->authType is always NONE... */ + VIR_SECRET_USAGE_TYPE_ISCSI, + &password, + &passwordlen) < 0) + { + /* FIXME: message */ + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("failed to get auth secret for storage")); + return -1; + } + + if ((proc->authfd = qemuNbdkitForkHandler(password, passwordlen)) < 0) + return -1; + + return 0; +} + + +qemuNbdkitProcess * +qemuNbdkitProcessLoad(virStorageSource *source, + const char *pidfile, + const char *socketfile) +{ + int rc; + qemuNbdkitProcess *nbdkit = g_new0(qemuNbdkitProcess, 1); + + nbdkit->pidfile = g_strdup(pidfile); + nbdkit->socketfile = g_strdup(socketfile); + nbdkit->source = virObjectRef(source); + nbdkit->user = -1; + nbdkit->group = -1; + + if ((rc = virPidFileReadPath(nbdkit->pidfile, &nbdkit->pid)) < 0) + VIR_WARN("Failed to read pidfile %s", nbdkit->pidfile); + + return nbdkit; +} + + +static qemuNbdkitProcess * +qemuNbdkitProcessNew(qemuNbdkitCaps *caps, + virStorageSource *source, + char *statedir, + const char *alias, + uid_t user, + gid_t group + /*, char *selinux_label*/) +{ + qemuNbdkitProcess *proc = g_new0(qemuNbdkitProcess, 1); + g_autofree char *pidfile = g_strdup_printf("nbdkit-%s.pid", alias); + g_autofree char *socketfile = g_strdup_printf("nbdkit-%s.socket", alias); + + proc->caps = g_object_ref(caps); + /* weak reference -- source owns this object, so it will always outlive us */ + proc->source = source; + proc->user = user; + proc->group = group; + proc->pidfile = g_build_filename(statedir, pidfile, NULL); + proc->socketfile = g_build_filename(statedir, socketfile, NULL); + + return proc; +} + + +void +qemuNbdkitInitStorageSource(qemuNbdkitCaps *caps, + virStorageSource *source, + char *statedir, + const char *alias, + uid_t user, + gid_t group + /*, char *selinux_label*/) +{ + /* FIXME: onlytarget ??? */ + qemuDomainStorageSourcePrivate *srcPriv = qemuDomainStorageSourcePrivateFetch(source); + + if (srcPriv->nbdkitProcess) + return; + + 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; + break; + case VIR_STORAGE_NET_PROTOCOL_SSH: + if (!virBitmapIsBitSet(caps->flags, QEMU_NBDKIT_CAPS_PLUGIN_SSH)) + return; + 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; + } + srcPriv->nbdkitProcess = qemuNbdkitProcessNew(caps, source, statedir, alias, user, group); +} + + +static void +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) { + virCommandAddArgPair(cmd, "user", + proc->source->auth->username); + } + + // FIXME: if (srcPriv->secinfo)??? + if (proc->authfd > 0) { + /* nbdkit auth parameter accepts a variation where nbdkit + * will read the cookies from a file descriptor: password=-FD */ + g_autofree char *fdfmt = g_strdup_printf("-%i", proc->authfd); + virCommandAddArgPair(cmd, "password", fdfmt); + virCommandPassFD(cmd, proc->authfd, VIR_COMMAND_PASS_FD_CLOSE_PARENT); + } + + if (proc->cookiefd > 0) { + /* nbdkit cookie parameter accepts a variation where nbdkit + * will read the cookies from a file descriptor: cookie=-FD */ + g_autofree char *fdfmt = g_strdup_printf("-%i", proc->cookiefd); + virCommandAddArgPair(cmd, "cookie", fdfmt); + virCommandPassFD(cmd, proc->cookiefd, VIR_COMMAND_PASS_FD_CLOSE_PARENT); + } + + 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); + } +} + + +static void +qemuNbdkitProcessBuildCommandSSH(qemuNbdkitProcess *proc, + virCommand *cmd) +{ + char *user = NULL; + virStorageNetHostDef *host = &proc->source->hosts[0]; + + /* nbdkit plugin name */ + virCommandAddArg(cmd, "ssh"); + + virCommandAddArgPair(cmd, "host", g_strdup(host->name)); + virCommandAddArgPair(cmd, "port", g_strdup_printf("%u", + host->port)); + virCommandAddArgPair(cmd, "path", g_strdup(proc->source->path)); + + if (proc->source->auth) { + user = g_strdup(proc->source->auth->username); + } else if (proc->source->ssh_user) { + user = g_strdup(proc->source->ssh_user); + } + + if (user) { + virCommandAddArgPair(cmd, "user", user); + } + + if (proc->source->ssh_host_key_check_disabled) { + virCommandAddArgPair(cmd, "verify-remote-host", + g_strdup("false")); + } +} + + +static virCommand * +qemuNbdkitProcessBuildCommand(qemuNbdkitProcess *proc) +{ + g_autoptr(virCommand) cmd = virCommandNewArgList(proc->caps->path, + "--exit-with-parent", + "--unix", + proc->socketfile, + "--foreground", + "--pidfile", + proc->pidfile, + "--verbose", + //"--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: + qemuNbdkitProcessBuildCommandCurl(proc, cmd); + break; + case VIR_STORAGE_NET_PROTOCOL_SSH: + qemuNbdkitProcessBuildCommandSSH(proc, cmd); + 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) +{ + if (virProcessKillPainfully(proc->pid, true) < 0) + VIR_WARN("Unable to kill nbdkit process"); + + unlink(proc->pidfile); + g_clear_pointer(&proc->pidfile, g_free); + unlink(proc->socketfile); + g_clear_pointer(&proc->socketfile, g_free); + VIR_FORCE_CLOSE(proc->cookiefd); + VIR_FORCE_CLOSE(proc->authfd); + g_clear_object(&proc->caps); + g_free(proc); +} + + +int +qemuNbdkitProcessSetupCgroup(qemuNbdkitProcess *proc, + virCgroup *cgroup) +{ + return virCgroupAddProcess(cgroup, proc->pid); +} + + +/* FIXME: selinux permissions errors */ +int +qemuNbdkitProcessStart(qemuNbdkitProcess *proc, + virDomainObj *vm, + virQEMUDriver *driver) +{ + g_autoptr(virCommand) cmd = NULL; + int rc; + int exitstatus = 0; + int cmdret = 0; + unsigned int loops = 0; + int errfd = -1; + + if (qemuNbdkitProcessForkCookieHandler(proc) < 0) + return -1; + + if (qemuNbdkitProcessForkPasswordHandler(proc) < 0) + return -1; + + if (!(cmd = qemuNbdkitProcessBuildCommand(proc))) + return -1; + + virCommandSetErrorFD(cmd, &errfd); + + 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; + } + + /* Wait for the pid file to appear. The file doesn't appear until nbdkit is + * ready to accept connections to the socket */ + while (!virFileExists(proc->pidfile)) { + VIR_DEBUG("waiting for nbdkit pidfile %s: %u", proc->pidfile, loops); + /* wait for 100ms before checking again, but don't do it for ever */ + if (errno == ENOENT && loops < 10) { + g_usleep(100 * 1000); + loops++; + } else { + char errbuf[1024] = { 0 }; + if (errfd && saferead(errfd, errbuf, sizeof(errbuf) - 1) > 0) { + virReportError(VIR_ERR_OPERATION_FAILED, + _("nbdkit failed to start: %s"), errbuf); + } else { + virReportSystemError(errno, "%s", + _("Gave up waiting for nbdkit to start")); + } + goto error; + } + } + + if ((rc = virPidFileReadPath(proc->pidfile, &proc->pid)) < 0) { + virReportSystemError(-rc, + _("Failed to read pidfile %s"), + proc->pidfile); + goto error; + } + + return 0; + + error: + if (proc->pid) + virProcessKillPainfully(proc->pid, true); + return -1; +} + + +int +qemuNbdkitProcessStop(qemuNbdkitProcess *proc) +{ + return virProcessKillPainfully(proc->pid, true); +} diff --git a/src/qemu/qemu_nbdkit.h b/src/qemu/qemu_nbdkit.h new file mode 100644 index 0000000000..628b5010cc --- /dev/null +++ b/src/qemu/qemu_nbdkit.h @@ -0,0 +1,89 @@ +/* + * 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 "virbitmap.h" +#include "vircgroup.h" +#include "vircommand.h" +#include "virstorageobj.h" +#include "viruri.h" + +typedef struct _qemuNbdkitCaps qemuNbdkitCaps; +typedef struct _qemuNbdkitProcess qemuNbdkitProcess; + +typedef enum { + QEMU_NBDKIT_CAPS_PLUGIN_CURL, + QEMU_NBDKIT_CAPS_PLUGIN_SSH, + QEMU_NBDKIT_CAPS_FILTER_READAHEAD, + QEMU_NBDKIT_CAPS_LAST, +} qemuNbdkitCapsFlags; + +qemuNbdkitCaps* qemuNbdkitCapsQuery(void); + +qemuNbdkitCaps* qemuNbdkitCapsNew(const char *path); + +void qemuNbdkitInitStorageSource(qemuNbdkitCaps *nbdkitCaps, + virStorageSource *source, + char *statedir, + const char *alias, + uid_t user, + gid_t group + /*, char *selinux_label*/); + +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); + +struct _qemuNbdkitProcess { + qemuNbdkitCaps *caps; + virStorageSource *source; + + char *pidfile; + char *socketfile; + int cookiefd; + int authfd; + uid_t user; + gid_t group; + pid_t pid; +}; + +typedef struct _virQEMUDriver virQEMUDriver; + +int qemuNbdkitProcessStart(qemuNbdkitProcess *proc, + virDomainObj *vm, + virQEMUDriver *driver); + +qemuNbdkitProcess * qemuNbdkitProcessLoad(virStorageSource *source, + const char *pidfile, + const char *socketfile); + +int qemuNbdkitProcessSetupCgroup(qemuNbdkitProcess *proc, virCgroup *cgroup); + +int qemuNbdkitProcessStop(qemuNbdkitProcess *proc); + +void qemuNbdkitProcessFree(qemuNbdkitProcess *proc); + +G_DEFINE_AUTOPTR_CLEANUP_FUNC(qemuNbdkitProcess, qemuNbdkitProcessFree); diff --git a/src/qemu/qemu_validate.c b/src/qemu/qemu_validate.c index 39210ba65b..b6cc75b651 100644 --- a/src/qemu/qemu_validate.c +++ b/src/qemu/qemu_validate.c @@ -602,7 +602,8 @@ qemuValidateDomainDefPM(const virDomainDef *def, static int qemuValidateDomainDefNvram(const virDomainDef *def, - virQEMUCaps *qemuCaps) + virQEMUCaps *qemuCaps, + qemuNbdkitCaps *nbdkitCaps) { virStorageSource *src = def->os.loader->nvram; @@ -655,7 +656,7 @@ qemuValidateDomainDefNvram(const virDomainDef *def, return -1; } - if (qemuDomainValidateStorageSource(src, qemuCaps, false) < 0) + if (qemuDomainValidateStorageSource(src, qemuCaps, nbdkitCaps, false) < 0) return -1; return 0; @@ -664,7 +665,8 @@ qemuValidateDomainDefNvram(const virDomainDef *def, static int qemuValidateDomainDefBoot(const virDomainDef *def, - virQEMUCaps *qemuCaps) + virQEMUCaps *qemuCaps, + qemuNbdkitCaps *nbdkitCaps) { if (def->os.loader) { if (def->os.loader->secure == VIR_TRISTATE_BOOL_YES) { @@ -695,7 +697,7 @@ qemuValidateDomainDefBoot(const virDomainDef *def, } } - if (qemuValidateDomainDefNvram(def, qemuCaps) < 0) + if (qemuValidateDomainDefNvram(def, qemuCaps, nbdkitCaps) < 0) return -1; } @@ -1167,6 +1169,7 @@ qemuValidateDomainDef(const virDomainDef *def, virQEMUDriver *driver = opaque; g_autoptr(virQEMUCaps) qemuCapsLocal = NULL; virQEMUCaps *qemuCaps = parseOpaque; + g_autoptr(qemuNbdkitCaps) nbdkitCaps = qemuGetNbdkitCaps(driver); size_t i; if (!qemuCaps) { @@ -1277,7 +1280,7 @@ qemuValidateDomainDef(const virDomainDef *def, if (qemuValidateDomainDefPM(def, qemuCaps) < 0) return -1; - if (qemuValidateDomainDefBoot(def, qemuCaps) < 0) + if (qemuValidateDomainDefBoot(def, qemuCaps, nbdkitCaps) < 0) return -1; if (qemuValidateDomainVCpuTopology(def, qemuCaps) < 0) @@ -3240,7 +3243,8 @@ qemuValidateDomainDeviceDefDiskTransient(const virDomainDiskDef *disk, int qemuValidateDomainDeviceDefDisk(const virDomainDiskDef *disk, const virDomainDef *def, - virQEMUCaps *qemuCaps) + virQEMUCaps *qemuCaps, + qemuNbdkitCaps *nbdkitCaps) { const char *driverName = virDomainDiskGetDriver(disk); bool isSD = qemuDiskBusIsSD(disk->bus); @@ -3287,7 +3291,7 @@ qemuValidateDomainDeviceDefDisk(const virDomainDiskDef *disk, for (n = disk->src; virStorageSourceIsBacking(n); n = n->backingStore) { /* blockdev support is masked out for 'sd' disks */ - if (qemuDomainValidateStorageSource(n, qemuCaps, isSD) < 0) + if (qemuDomainValidateStorageSource(n, qemuCaps, nbdkitCaps, isSD) < 0) return -1; } @@ -5234,6 +5238,7 @@ qemuValidateDomainDeviceDef(const virDomainDeviceDef *dev, virQEMUDriver *driver = opaque; g_autoptr(virQEMUCaps) qemuCapsLocal = NULL; virQEMUCaps *qemuCaps = parseOpaque; + g_autoptr(qemuNbdkitCaps) nbdkitcaps = qemuGetNbdkitCaps(driver); if (!qemuCaps) { if (!(qemuCapsLocal = virQEMUCapsCacheLookup(driver->qemuCapsCache, @@ -5273,7 +5278,8 @@ qemuValidateDomainDeviceDef(const virDomainDeviceDef *dev, return qemuValidateDomainDeviceDefVideo(dev->data.video, qemuCaps); case VIR_DOMAIN_DEVICE_DISK: - return qemuValidateDomainDeviceDefDisk(dev->data.disk, def, qemuCaps); + return qemuValidateDomainDeviceDefDisk(dev->data.disk, def, qemuCaps, + nbdkitcaps); case VIR_DOMAIN_DEVICE_CONTROLLER: return qemuValidateDomainDeviceDefController(dev->data.controller, def, diff --git a/src/qemu/qemu_validate.h b/src/qemu/qemu_validate.h index e06a43b8e3..1498a978ed 100644 --- a/src/qemu/qemu_validate.h +++ b/src/qemu/qemu_validate.h @@ -21,6 +21,7 @@ #pragma once #include "qemu_capabilities.h" +#include "qemu_nbdkit.h" int qemuValidateDomainDef(const virDomainDef *def, @@ -30,7 +31,8 @@ qemuValidateDomainDef(const virDomainDef *def, int qemuValidateDomainDeviceDefDisk(const virDomainDiskDef *disk, const virDomainDef *def, - virQEMUCaps *qemuCaps); + virQEMUCaps *qemuCaps, + qemuNbdkitCaps *nbdkitCaps); int qemuValidateDomainDeviceDef(const virDomainDeviceDef *dev, diff --git a/src/util/virerror.c b/src/util/virerror.c index d114c0a346..5b665931ca 100644 --- a/src/util/virerror.c +++ b/src/util/virerror.c @@ -145,6 +145,7 @@ VIR_ENUM_IMPL(virErrorDomain, "TPM", /* 70 */ "BPF", "Cloud-Hypervisor Driver", + "Nbdkit", ); diff --git a/tests/qemublocktest.c b/tests/qemublocktest.c index 57116c930b..18dd1f61fe 100644 --- a/tests/qemublocktest.c +++ b/tests/qemublocktest.c @@ -280,7 +280,8 @@ testQemuDiskXMLToProps(const void *opaque) virDomainDiskInsert(vmdef, disk); - if (qemuValidateDomainDeviceDefDisk(disk, vmdef, data->qemuCaps) < 0) { + /* FIXME: test with nbdkit */ + if (qemuValidateDomainDeviceDefDisk(disk, vmdef, data->qemuCaps, NULL) < 0) { VIR_TEST_VERBOSE("invalid configuration for disk"); return -1; } @@ -294,7 +295,8 @@ testQemuDiskXMLToProps(const void *opaque) if (testQemuDiskXMLToJSONFakeSecrets(n) < 0) return -1; - if (qemuDomainValidateStorageSource(n, data->qemuCaps, false) < 0) + /* FIXME: test with nbdkit */ + if (qemuDomainValidateStorageSource(n, data->qemuCaps, NULL, false) < 0) return -1; qemuDomainPrepareDiskSourceData(disk, n); @@ -519,7 +521,7 @@ testQemuImageCreate(const void *opaque) src->capacity = UINT_MAX * 2ULL; src->physical = UINT_MAX + 1ULL; - if (qemuDomainValidateStorageSource(src, data->qemuCaps, false) < 0) + if (qemuDomainValidateStorageSource(src, data->qemuCaps, NULL, false) < 0) return -1; if (qemuBlockStorageSourceCreateGetStorageProps(src, &protocolprops) < 0) diff --git a/tests/qemustatusxml2xmldata/modern-in.xml b/tests/qemustatusxml2xmldata/modern-in.xml index 7759034f7a..49b005cfc7 100644 --- a/tests/qemustatusxml2xmldata/modern-in.xml +++ b/tests/qemustatusxml2xmldata/modern-in.xml @@ -337,7 +337,6 @@ <objects> <secret type='auth' alias='test-auth-alias'/> <secret type='encryption' alias='test-encryption-alias'/> - <secret type='httpcookie' alias='http-cookie-alias'/> <secret type='tlskey' alias='tls-certificate-key-alias'/> <TLSx509 alias='transport-alias'/> </objects> 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..c944a3ead4 --- /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-ide0-0-0.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-ide0-0-1.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-ide0-1-0.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..378dcc3e62 --- /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-virtio-disk0.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-virtio-disk1.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-virtio-disk2.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-virtio-disk3.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.x86_64-latest.args b/tests/qemuxml2argvdata/disk-network-source-curl-nbdkit.x86_64-latest.args new file mode 100644 index 0000000000..2410d8dfa5 --- /dev/null +++ b/tests/qemuxml2argvdata/disk-network-source-curl-nbdkit.x86_64-latest.args @@ -0,0 +1,49 @@ +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-virtio-disk0.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}' \ +-blockdev '{"driver":"nbd","server":{"type":"unix","path":"/tmp/lib/domain--1-QEMUGuest1/nbdkit-virtio-disk4.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-sata0-0-1.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-sata0-0-2.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-sata0-0-3.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/qemuxml2argvtest.c b/tests/qemuxml2argvtest.c index 48dd20458e..379ea307f8 100644 --- a/tests/qemuxml2argvtest.c +++ b/tests/qemuxml2argvtest.c @@ -705,6 +705,11 @@ testCompareXMLToArgv(const void *data) if (rc < 0) goto cleanup; + if (info->nbdkitCaps) { + driver.nbdkitCaps = info->nbdkitCaps; + g_object_add_weak_pointer(G_OBJECT(info->nbdkitCaps), (void**)&driver.nbdkitCaps); + } + if (info->migrateFrom && !(migrateURI = qemuMigrationDstGetURI(info->migrateFrom, info->migrateFd))) @@ -963,6 +968,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") @@ -1330,6 +1338,7 @@ mymain(void) DO_TEST_CAPS_LATEST("disk-cdrom-bus-other"); DO_TEST_CAPS_VER("disk-cdrom-network", "4.1.0"); DO_TEST_CAPS_LATEST("disk-cdrom-network"); + DO_TEST_CAPS_LATEST_NBDKIT("disk-cdrom-network-nbdkit", QEMU_NBDKIT_CAPS_PLUGIN_CURL); DO_TEST_CAPS_VER("disk-cdrom-tray", "4.1.0"); DO_TEST_CAPS_LATEST("disk-cdrom-tray"); DO_TEST_CAPS_VER("disk-floppy", "4.1.0"); @@ -1390,6 +1399,8 @@ mymain(void) DO_TEST_CAPS_VER("disk-network-sheepdog", "6.0.0"); DO_TEST_CAPS_VER("disk-network-source-auth", "4.1.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("disk-network-nfs"); driver.config->vxhsTLS = 1; driver.config->nbdTLSx509secretUUID = g_strdup("6fd3f62d-9fe7-4a4e-a869-7acd6376d8ea"); @@ -1402,6 +1413,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_NBDKIT("disk-network-http-nbdkit", QEMU_NBDKIT_CAPS_PLUGIN_CURL); driver.config->vxhsTLS = 0; VIR_FREE(driver.config->vxhsTLSx509certdir); DO_TEST_CAPS_LATEST("disk-no-boot"); diff --git a/tests/testutilsqemu.c b/tests/testutilsqemu.c index 6dabbaf36a..65b403c991 100644 --- a/tests/testutilsqemu.c +++ b/tests/testutilsqemu.c @@ -395,6 +395,7 @@ void qemuTestDriverFree(virQEMUDriver *driver) virObjectUnref(driver->caps); virObjectUnref(driver->config); virObjectUnref(driver->securityManager); + g_clear_object(&driver->nbdkitCaps); virCPUDefFree(cpuDefault); virCPUDefFree(cpuHaswell); @@ -632,6 +633,8 @@ int qemuTestDriverInit(virQEMUDriver *driver) if (!driver->caps) goto error; + driver->nbdkitCaps = NULL; + /* Using /dev/null for libDir and cacheDir automatically produces errors * upon attempt to use any of them */ driver->qemuCapsCache = virQEMUCapsCacheNew("/dev/null", "/dev/null", 0, 0); @@ -858,6 +861,7 @@ testQemuInfoSetArgs(struct testQemuInfo *info, info->conf = conf; info->args.newargs = true; + info->args.fakeNbdkitCaps = qemuNbdkitCapsNew("/bin/true"); va_start(argptr, conf); while ((argname = va_arg(argptr, testQemuInfoArgName)) != ARG_END) { @@ -869,6 +873,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; @@ -993,6 +1004,9 @@ testQemuInfoInitArgs(struct testQemuInfo *info) info->qemuCaps = g_steal_pointer(&info->args.fakeCaps); } + if (info->args.fakeNbdkitCapsUsed) + info->nbdkitCaps = g_steal_pointer(&info->args.fakeNbdkitCaps); + if (info->args.gic != GIC_NONE && testQemuCapsSetGIC(info->qemuCaps, info->args.gic) < 0) return -1; @@ -1010,6 +1024,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 7ce4c4ad8d..7d407004b4 100644 --- a/tests/testutilsqemu.h +++ b/tests/testutilsqemu.h @@ -49,6 +49,7 @@ typedef enum { ARG_CAPS_VER, ARG_CAPS_HOST_CPU_MODEL, ARG_HOST_OS, + ARG_NBDKIT_CAPS, ARG_END, } testQemuInfoArgName; @@ -79,6 +80,8 @@ struct testQemuArgs { bool newargs; virQEMUCaps *fakeCaps; bool fakeCapsUsed; + qemuNbdkitCaps *fakeNbdkitCaps; + bool fakeNbdkitCapsUsed; char *capsver; char *capsarch; qemuTestCPUDef capsHostCPUModel; @@ -93,6 +96,7 @@ struct testQemuInfo { char *outfile; char *errfile; virQEMUCaps *qemuCaps; + qemuNbdkitCaps *nbdkitCaps; const char *migrateFrom; int migrateFd; unsigned int flags; -- 2.35.3