On qemu-5.2.0-4.fc34.x86_64, libvirt v6.10.0-262-gbed50bcbbb with this patch series, by following script:
#!/bin/bash -
set -o nounset                              # Treat unset variables as an error
VM=test
NFS_EXPORT=/tmp/nfs
IMG=disk1
DISK_XML=/tmp/nfs.xml
mkdir -p $NFS_EXPORT
echo "$NFS_EXPORT *(rw,no_root_squash,insecure)" > /etc/exports

systemctl restart nfs-server

qemu-img create nfs://localhost"$NFS_EXPORT/$IMG" 100M
chmod 777 $NFS_EXPORT -R

virsh start $VM --console

echo "<disk type='network' device='disk'>
      <driver name='qemu' type='raw' cache='none'/>
      <source protocol='nfs' name='$NFS_EXPORT/$IMG'>
        <host name='localhost'/>
        <nfs user='+107' group='+107'/>
      </source>
      <target dev='vdb' bus='virtio'/>
    </disk>" > $DISK_XML
virsh attach-device $VM $DISK_XML
virsh console $VM
virsh detach-device $VM $DISK_XML
virsh console $VM

virsh destroy $VM
virsh attach-device $VM $DISK_XML --config
virsh start $VM --console
virsh destroy $VM
virsh detach-device $VM $DISK_XML --config

It works for me. All attach/detach tests have passed.

On Wed, Dec 30, 2020 at 5:23 AM Ryan Gahagan <rgahagan@cs.utexas.edu> wrote:
Signed-off-by: Ryan Gahagan <rgahagan@cs.utexas.edu>
---
 src/qemu/qemu_block.c  | 79 +++++++++++++++++++++++++++++++++++++++++-
 src/qemu/qemu_domain.c | 47 +++++++++++++++++++++++++
 2 files changed, 125 insertions(+), 1 deletion(-)

diff --git a/src/qemu/qemu_block.c b/src/qemu/qemu_block.c
index b224a550f3..5413a4f0e8 100644
--- a/src/qemu/qemu_block.c
+++ b/src/qemu/qemu_block.c
@@ -574,6 +574,35 @@ qemuBlockStorageSourceBuildJSONInetSocketAddress(virStorageNetHostDefPtr host)
 }


+/**
+ * qemuBlockStorageSourceBuildJSONNFSServer(virStorageNetHostDefPtr host)
+ * @host: the virStorageNetHostDefPtr definition to build
+ *
+ * Formats @hosts into a json object conforming to the 'NFSServer' type
+ * in qemu.
+ *
+ * Returns a virJSONValuePtr for a single server.
+ */
+static virJSONValuePtr
+qemuBlockStorageSourceBuildJSONNFSServer(virStorageNetHostDefPtr host)
+{
+    virJSONValuePtr ret = NULL;
+
+    if (host->transport != VIR_STORAGE_NET_HOST_TRANS_TCP) {
+        virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
+                       _("only TCP protocol can be converted to NFSServer"));
+        return NULL;
+    }
+
+    ignore_value(virJSONValueObjectCreate(&ret,
+                                          "s:host", host->name,
+                                          "s:type", "inet",
+                                          NULL));
+
+    return ret;
+}
+
+
 /**
  * qemuBlockStorageSourceBuildHostsJSONInetSocketAddress:
  * @src: disk storage source
@@ -674,6 +703,44 @@ qemuBlockStorageSourceGetVxHSProps(virStorageSourcePtr src,
 }


+static virJSONValuePtr
+qemuBlockStorageSourceGetNFSProps(virStorageSourcePtr src)
+{
+    g_autoptr(virJSONValue) server = NULL;
+    virJSONValuePtr ret = NULL;
+
+    if (src->nhosts != 1) {
+        virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
+                       _("NFS protocol accepts only one host"));
+        return NULL;
+    }
+
+    if (!(server = qemuBlockStorageSourceBuildJSONNFSServer(&src->hosts[0])))
+        return NULL;
+
+    /* NFS disk specification example:
+     * { driver:"nfs",
+     *   user: "0",
+     *   group: "0",
+     *   path: "/foo/bar/baz",
+     *   server: {type:"tcp", host:"1.2.3.4"}}
+     */
+    ignore_value(virJSONValueObjectCreate(&ret,
+                                          "a:server", &server,
+                                          "S:path", src->path, NULL));
+
+    if (src->nfs_uid != -1 &&
+        virJSONValueObjectAdd(ret, "i:user", src->nfs_uid, NULL) < 0)
+        return NULL;
+
+    if (src->nfs_gid != -1 &&
+        virJSONValueObjectAdd(ret, "i:group", src->nfs_gid, NULL) < 0)
+        return NULL;
+
+    return ret;
+}
+
+
 static virJSONValuePtr
 qemuBlockStorageSourceGetCURLProps(virStorageSourcePtr src,
                                    bool onlytarget)
@@ -1181,6 +1248,11 @@ qemuBlockStorageSourceGetBackendProps(virStorageSourcePtr src,
             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);
@@ -2500,11 +2572,16 @@ qemuBlockStorageSourceCreateGetStorageProps(virStorageSourcePtr src,
                 return -1;
             break;

+        case VIR_STORAGE_NET_PROTOCOL_NFS:
+            driver = "nfs";
+            if (!(location = qemuBlockStorageSourceGetNFSProps(src)))
+                return -1;
+            break;
+
             /* unsupported/impossible */
         case VIR_STORAGE_NET_PROTOCOL_NBD:
         case VIR_STORAGE_NET_PROTOCOL_ISCSI:
         case VIR_STORAGE_NET_PROTOCOL_VXHS:
-        case VIR_STORAGE_NET_PROTOCOL_NFS:
         case VIR_STORAGE_NET_PROTOCOL_HTTP:
         case VIR_STORAGE_NET_PROTOCOL_HTTPS:
         case VIR_STORAGE_NET_PROTOCOL_FTP:
diff --git a/src/qemu/qemu_domain.c b/src/qemu/qemu_domain.c
index d91c32b2c5..8812df5138 100644
--- a/src/qemu/qemu_domain.c
+++ b/src/qemu/qemu_domain.c
@@ -4626,6 +4626,14 @@ qemuDomainValidateStorageSource(virStorageSourcePtr src,
         return -1;
     }

+    /* NFS protocol may only be used if current QEMU supports blockdev */
+    if (actualType == VIR_STORAGE_TYPE_NETWORK &&
+        src->protocol == VIR_STORAGE_NET_PROTOCOL_NFS &&
+        !blockdev) {
+        virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
+                       _("'nfs' protocol is not supported with this QEMU binary"));
+    }
+
     return 0;
 }

@@ -9590,6 +9598,42 @@ qemuProcessPrepareStorageSourceTLSNBD(virStorageSourcePtr src,
 }


+/* qemuPrepareStorageSourceNFS:
+ * @src: source for a disk
+ *
+ * If src is an NFS source, translate nfs_user and nfs_group
+ * into a uid and gid field. If these strings are empty (ie "")
+ * then provide the hypervisor default uid and gid.
+ */
+static int
+qemuDomainPrepareStorageSourceNFS(virStorageSourcePtr src)
+{
+    if (virStorageSourceGetActualType(src) != VIR_STORAGE_TYPE_NETWORK)
+        return 0;
+
+    if ((virStorageNetProtocol) src->protocol != VIR_STORAGE_NET_PROTOCOL_NFS)
+        return 0;
+
+    if (src->nfs_user) {
+        if (virGetUserID(src->nfs_user, &src->nfs_uid) < 0)
+            return -1;
+    } else {
+        /* -1 indicates default UID */
+        src->nfs_uid = -1;
+    }
+
+    if (src->nfs_group) {
+        if (virGetGroupID(src->nfs_group, &src->nfs_gid) < 0)
+            return -1;
+    } else {
+        /* -1 indicates default GID */
+        src->nfs_gid = -1;
+    }
+
+    return 0;
+}
+
+
 /* qemuProcessPrepareStorageSourceTLS:
  * @source: source for a disk
  * @cfg: driver configuration
@@ -10401,6 +10445,9 @@ qemuDomainPrepareStorageSourceBlockdev(virDomainDiskDefPtr disk,
                                           priv) < 0)
         return -1;

+    if (qemuDomainPrepareStorageSourceNFS(src) < 0)
+        return -1;
+
     return 0;
 }

--
2.29.2



--
Tested-by: Han Han <hhan@redhat.com>