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(a)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