[Libguestfs] [nbdkit PATCH 3/3] nbd: Implement .list_exports

Eric Blake eblake at redhat.com
Fri Aug 28 21:22:14 UTC 2020


With new-enough libnbd and our new dynamic-export mode, we can grab
the export list from the server for replay to the client.

Signed-off-by: Eric Blake <eblake at redhat.com>
---
 plugins/nbd/nbdkit-nbd-plugin.pod |   7 ++
 tests/Makefile.am                 |   2 +
 plugins/nbd/nbd.c                 |  53 ++++++++++
 tests/test-nbd-dynamic-content.sh |   2 +-
 tests/test-nbd-dynamic-list.sh    | 162 ++++++++++++++++++++++++++++++
 5 files changed, 225 insertions(+), 1 deletion(-)
 create mode 100755 tests/test-nbd-dynamic-list.sh

diff --git a/plugins/nbd/nbdkit-nbd-plugin.pod b/plugins/nbd/nbdkit-nbd-plugin.pod
index 55f6fff0..5820ada8 100644
--- a/plugins/nbd/nbdkit-nbd-plugin.pod
+++ b/plugins/nbd/nbdkit-nbd-plugin.pod
@@ -168,6 +168,13 @@ see different content when the server differentiates content by export
 name.  Dynamic exports prevent the use of C<shared> mode, and thus are
 not usable with C<command> or C<socket-fd>.

+If libnbd is new enough, dynamic export mode is able to advertise the
+same exports as listed by the server; C<nbdkit --dump-plugin nbd> will
+contain C<libnbd_dynamic_list=1> if this is the case.  Regardless of
+what this plugin lists, it is also possible to use
+L<nbdkit-exportname-filter(1)> to adjust what export names the client
+sees or uses as a default.
+
 =item B<tls=off>

 =item B<tls=on>
diff --git a/tests/Makefile.am b/tests/Makefile.am
index 14e9abdb..cbbc750a 100644
--- a/tests/Makefile.am
+++ b/tests/Makefile.am
@@ -763,6 +763,7 @@ if HAVE_LIBNBD
 LIBGUESTFS_TESTS += test-nbd
 TESTS += \
 	test-nbd-dynamic-content.sh \
+	test-nbd-dynamic-list.sh \
 	test-nbd-extents.sh \
 	test-nbd-qcow2.sh \
 	test-nbd-tls.sh \
@@ -771,6 +772,7 @@ TESTS += \
 	$(NULL)
 EXTRA_DIST += \
 	test-nbd-dynamic-content.sh \
+	test-nbd-dynamic-list.sh \
 	test-nbd-extents.sh \
 	test-nbd-qcow2.sh \
 	test-nbd-tls.sh \
diff --git a/plugins/nbd/nbd.c b/plugins/nbd/nbd.c
index 7389b6d9..c2d2d166 100644
--- a/plugins/nbd/nbd.c
+++ b/plugins/nbd/nbd.c
@@ -407,6 +407,11 @@ nbdplug_dump_plugin (void)
   printf ("libnbd_tls=%d\n", nbd_supports_tls (nbd));
   printf ("libnbd_uri=%d\n", nbd_supports_uri (nbd));
   printf ("libnbd_vsock=%d\n", USE_VSOCK);
+#if LIBNBD_HAVE_NBD_OPT_LIST
+  printf ("libnbd_dynamic_list=1\n");
+#else
+  printf ("libnbd_dynamic_list=0\n");
+#endif
   nbd_close (nbd);
 }

@@ -685,6 +690,53 @@ nbdplug_open_handle (int readonly, const char *client_export)
   return NULL;
 }

+#if LIBNBD_HAVE_NBD_OPT_LIST
+static int
+collect_one (void *opaque, const char *name, const char *desc)
+{
+  struct nbdkit_exports *exports = opaque;
+
+  if (nbdkit_add_export (exports, name, desc) == -1)
+    nbdkit_debug ("Unable to share export %s: %s", name, nbd_get_error ());
+  return 0;
+}
+#endif /* LIBNBD_HAVE_NBD_OPT_LIST */
+
+/* Export list. */
+static int
+nbdplug_list_exports (int readonly, int is_tls, struct nbdkit_exports *exports)
+{
+#if LIBNBD_HAVE_NBD_OPT_LIST
+  if (dynamic_export) {
+    struct nbd_handle *nbd = nbd_create ();
+    int r = -1;
+
+    if (!nbd)
+      goto out;
+    if (nbd_set_opt_mode (nbd, 1) == -1)
+      goto out;
+    if (nbdplug_connect (nbd) == -1)
+      goto out;
+    if (nbd_opt_list (nbd, (nbd_list_callback) { .callback = collect_one,
+                                                 .user_data = exports }) == -1)
+      goto out;
+    r = 0;
+  out:
+    if (r == -1)
+      nbdkit_error ("Unable to get list: %s", nbd_get_error ());
+    if (nbd) {
+      if (nbd_aio_is_negotiating (nbd))
+        nbd_opt_abort (nbd);
+      else if (nbd_aio_is_ready (nbd))
+        nbd_shutdown (nbd, 0);
+      nbd_close (nbd);
+    }
+    return r;
+  }
+#endif
+  return nbdkit_add_default_export (exports);
+}
+
 /* Canonical name of default export. */
 static const char *
 nbdplug_default_export (int readonly, int is_tls)
@@ -1068,6 +1120,7 @@ static struct nbdkit_plugin plugin = {
   .magic_config_key   = "uri",
   .after_fork         = nbdplug_after_fork,
   .dump_plugin        = nbdplug_dump_plugin,
+  .list_exports       = nbdplug_list_exports,
   .default_export     = nbdplug_default_export,
   .open               = nbdplug_open,
   .close              = nbdplug_close,
diff --git a/tests/test-nbd-dynamic-content.sh b/tests/test-nbd-dynamic-content.sh
index bbd889e4..48d2c30c 100755
--- a/tests/test-nbd-dynamic-content.sh
+++ b/tests/test-nbd-dynamic-content.sh
@@ -35,7 +35,7 @@ set -e
 set -x

 # This test works with older libnbd, showing that dynamic mode affects
-# content.  XXX Also write a test, requiring newer libnbd, to show export list
+# content.
 requires_plugin info
 requires nbdsh --version

diff --git a/tests/test-nbd-dynamic-list.sh b/tests/test-nbd-dynamic-list.sh
new file mode 100755
index 00000000..419ec9bb
--- /dev/null
+++ b/tests/test-nbd-dynamic-list.sh
@@ -0,0 +1,162 @@
+#!/usr/bin/env bash
+# nbdkit
+# Copyright (C) 2019-2020 Red Hat Inc.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
+#
+# * Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+#
+# * Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# * Neither the name of Red Hat nor the names of its contributors may be
+# used to endorse or promote products derived from this software without
+# specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY RED HAT AND CONTRIBUTORS ''AS IS'' AND
+# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+# THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
+# PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL RED HAT OR
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
+# USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+# OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+# SUCH DAMAGE.
+
+source ./functions.sh
+set -e
+set -x
+
+# This test works with newer libnbd, showing that dynamic mode affects
+# export listing.
+requires_plugin sh
+requires nbdinfo --version
+
+# Does the nbd plugin support dynamic lists?
+if ! nbdkit --dump-plugin nbd | grep -sq libnbd_dynamic_list=1; then
+    echo "$0: nbd plugin built without dynamic export list support"
+    exit 77
+fi
+
+base=test-nbd-dynamic-list
+sock1=`mktemp -u`
+sock2=`mktemp -u`
+pid1="$base.pid1"
+pid2="$base.pid2"
+files="$sock1 $sock2 $pid1 $pid2 $base.list $base.out1 $base.out2"
+rm -f $files
+cleanup_fn rm -f $files
+
+fail=0
+
+# Start a long-running server with .list_exports and .default_export
+# set to varying contents
+start_nbdkit -P $pid1 -U $sock1 eval get_size='echo "$2"|wc -c' \
+    open='echo "$3"' list_exports="cat '$PWD/$base.list'" \
+    default_export="cat '$PWD/$base.list'"
+
+# Long-running nbd bridge, which should pass export list through
+start_nbdkit -P $pid2 -U $sock2 nbd socket=$sock1 dynamic-export=true
+
+# check_success_one EXPORT
+# - nbdinfo of EXPORT on both servers should succeed, with matching output
+check_success_one ()
+{
+    nbdinfo --no-content "nbd+unix:///$1?socket=$sock1" > $base.out1
+    nbdinfo --no-content "nbd+unix:///$1?socket=$sock2" > $base.out2
+    cat $base.out2
+    diff -u $base.out1 $base.out2
+}
+
+# check_success_list
+# - nbdinfo --list on both servers should succeed, with matching output
+check_success_list ()
+{
+    nbdinfo --list --json nbd+unix://\?socket=$sock1 > $base.out1
+    nbdinfo --list --json nbd+unix://\?socket=$sock2 > $base.out2
+    cat $base.out2
+    diff -u $base.out1 $base.out2
+}
+
+# check_success EXPORT... - both sub-tests, on all EXPORTs
+check_success()
+{
+    for exp; do
+        check_success_one "$exp"
+    done
+    check_success_list
+}
+
+# check_fail_one EXPORT
+# - nbdinfo of EXPORT on both servers should fail
+check_fail_one ()
+{
+    if nbdinfo --no-content "nbd+unix:///$1?socket=$sock1" > $base.out1; then
+        fail=1
+    fi
+    if nbdinfo --no-content "nbd+unix:///$1?socket=$sock2" > $base.out2; then
+        fail=1
+    fi
+}
+
+# check_fail_list
+# - nbdinfo --list on both servers should fail
+check_fail_list ()
+{
+    if nbdinfo --list --json nbd+unix://\?socket=$sock1 > $base.out1; then
+        fail=1
+    fi
+    if nbdinfo --list --json nbd+unix://\?socket=$sock2 > $base.out2; then
+        fail=1
+    fi
+}
+
+# With no file, list_exports and the default export fail,
+# but other exports work
+check_fail_one ""
+check_success_one name
+check_fail_list
+
+# With an empty list, there are 0 exports, and any export works
+touch $base.list
+check_success "" name
+
+# An explicit advertisement of the default export, any export works
+echo > $base.list
+check_success "" name
+
+# A non-empty default name
+echo name > $base.list
+check_success "" name
+
+# Multiple exports, with descriptions
+cat > $base.list <<EOF
+INTERLEAVED
+name1
+desc1
+name2
+desc2
+EOF
+echo name > $base.list
+check_success "" name1
+
+# Longest possible name and description
+long=$(printf %04096d 1)
+echo NAMES+DESCRIPTIONS > $base.list
+echo $long >> $base.list
+echo $long >> $base.list
+check_success "" $long
+
+# An invalid name prevents list, but we can still connect
+echo 2$long >> $base.list
+check_success_one ""
+check_fail_list
+
+exit $fail
-- 
2.28.0




More information about the Libguestfs mailing list