This test proves that an attempt to list meta contexts goes to the
server rather than being rejected at the client, even with older
nbdkit that fails the attempt[1]; and includes ports to the various
languages. Done as a separate commit to allow temporary reordering of
the series to prove the test works.
[1] nbdkit didn't accept LIST_META_CONTENT before STRUCTURED_REPLY
until commit c39cba73 [v1.27.3, backported to 1.26.6]. Similarly,
qemu-nbd didn't accept it until commit da24597d [v6.2]. So, I was
able to test the graceful skip by adding a build of nbdkit without
that support at the front of my PATH.
---
python/t/240-opt-list-meta.py | 25 ++++++++++
ocaml/tests/test_240_opt_list_meta.ml | 30 ++++++++++++
tests/opt-list-meta.c | 34 ++++++++++++--
golang/libnbd_240_opt_list_meta_test.go | 61 ++++++++++++++++++++++++-
4 files changed, 144 insertions(+), 6 deletions(-)
diff --git a/python/t/240-opt-list-meta.py b/python/t/240-opt-list-meta.py
index 2b66eb5d..50fcfd69 100644
--- a/python/t/240-opt-list-meta.py
+++ b/python/t/240-opt-list-meta.py
@@ -31,6 +31,7 @@ def f(user_data, name):
seen = True
+# Get into negotiating state.
h = nbd.NBD()
h.set_opt_mode(True)
h.connect_command(["nbdkit", "-s", "--exit-with-parent",
"-v",
@@ -77,3 +78,27 @@ def f(user_data, name):
assert seen is True
h.opt_abort()
+
+# Repeat but this time without structured replies. Deal gracefully
+# with older servers that don't allow the attempt.
+h = nbd.NBD()
+h.set_opt_mode(True)
+h.set_request_structured_replies(False)
+h.connect_command(["nbdkit", "-s", "--exit-with-parent",
"-v",
+ "memory", "size=1M"])
+bytes = h.stats_bytes_sent()
+
+try:
+ count = 0
+ seen = False
+ r = h.opt_list_meta_context(lambda *args: f(42, *args))
+ assert r == count
+ assert r >= 1
+ assert seen is True
+except nbd.Error as ex:
+ assert h.stats_bytes_sent() > bytes
+ print("ignoring failure from old server: %s" % ex.string)
+
+# FIXME: Once nbd_opt_structured_reply exists, use it here and retry.
+
+h.opt_abort()
diff --git a/ocaml/tests/test_240_opt_list_meta.ml
b/ocaml/tests/test_240_opt_list_meta.ml
index 639d33c8..64159185 100644
--- a/ocaml/tests/test_240_opt_list_meta.ml
+++ b/ocaml/tests/test_240_opt_list_meta.ml
@@ -17,6 +17,8 @@
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*)
+open Printf
+
let count = ref 0
let seen = ref false
let f user_data name =
@@ -27,6 +29,7 @@ let
0
let () =
+ (* Get into negotiating state. *)
let nbd = NBD.create () in
NBD.set_opt_mode nbd true;
NBD.connect_command nbd
@@ -75,6 +78,33 @@ let
assert (r = !count);
assert !seen;
+ NBD.opt_abort nbd;
+
+ (* Repeat but this time without structured replies. Deal gracefully
+ * with older servers that don't allow the attempt.
+ *)
+ let nbd = NBD.create () in
+ NBD.set_opt_mode nbd true;
+ NBD.set_request_structured_replies nbd false;
+ NBD.connect_command nbd
+ ["nbdkit"; "-s";
"--exit-with-parent"; "-v";
+ "memory"; "size=1M"];
+ let bytes = NBD.stats_bytes_sent nbd in
+ (try
+ count := 0;
+ seen := false;
+ let r = NBD.opt_list_meta_context nbd (f 42) in
+ assert (r = !count);
+ assert (r >= 1);
+ assert !seen
+ with
+ NBD.Error (errstr, errno) ->
+ assert (NBD.stats_bytes_sent nbd > bytes);
+ printf "ignoring failure from old server %s\n" errstr
+ );
+
+ (* FIXME: Once nbd_opt_structured_reply exists, use it here and retry. *)
+
NBD.opt_abort nbd
let () = Gc.compact ()
diff --git a/tests/opt-list-meta.c b/tests/opt-list-meta.c
index 9ad8e37e..dc9c6799 100644
--- a/tests/opt-list-meta.c
+++ b/tests/opt-list-meta.c
@@ -17,6 +17,7 @@
*/
/* Test behavior of nbd_opt_list_meta_context. */
+/* See also unit test 240 in the various language ports. */
#include <config.h>
@@ -56,6 +57,7 @@ main (int argc, char *argv[])
"memory", "size=1M", NULL };
int max;
char *tmp;
+ uint64_t bytes;
/* Get into negotiating state. */
nbd = nbd_create ();
@@ -164,7 +166,9 @@ main (int argc, char *argv[])
nbd_opt_abort (nbd);
nbd_close (nbd);
- /* Repeat but this time without structured replies. */
+ /* Repeat but this time without structured replies. Deal gracefully
+ * with older servers that don't allow the attempt.
+ */
nbd = nbd_create ();
if (nbd == NULL ||
nbd_set_opt_mode (nbd, true) == -1 ||
@@ -173,16 +177,36 @@ main (int argc, char *argv[])
fprintf (stderr, "%s\n", nbd_get_error ());
exit (EXIT_FAILURE);
}
+ bytes = nbd_stats_bytes_sent (nbd);
- /* FIXME: For now, we reject this client-side, but it is overly strict. */
p = (struct progress) { .count = 0 };
r = nbd_opt_list_meta_context (nbd,
(nbd_context_callback) { .callback = check,
.user_data = &p});
- if (r != -1) {
- fprintf (stderr, "not expecting command to succeed\n");
- exit (EXIT_FAILURE);
+ if (r == -1) {
+ if (nbd_stats_bytes_sent (nbd) == bytes) {
+ fprintf (stderr, "bug: client failed to send request\n");
+ exit (EXIT_FAILURE);
+ }
+ fprintf (stdout, "ignoring failure from old server: %s\n",
+ nbd_get_error ());
}
+ else {
+ if (r != p.count) {
+ fprintf (stderr, "inconsistent return value %d, expected %d\n",
+ r, p.count);
+ exit (EXIT_FAILURE);
+ }
+ if (r < 1 || !p.seen) {
+ fprintf (stderr, "server did not reply with base:allocation\n");
+ exit (EXIT_FAILURE);
+ }
+ }
+
+ /* FIXME: Once nbd_opt_structured_reply() exists, use it here and retry. */
+
+ nbd_opt_abort (nbd);
+ nbd_close (nbd);
exit (EXIT_SUCCESS);
}
diff --git a/golang/libnbd_240_opt_list_meta_test.go
b/golang/libnbd_240_opt_list_meta_test.go
index d7322752..2b49c895 100644
--- a/golang/libnbd_240_opt_list_meta_test.go
+++ b/golang/libnbd_240_opt_list_meta_test.go
@@ -1,5 +1,5 @@
/* libnbd golang tests
- * Copyright (C) 2013-2021 Red Hat Inc.
+ * Copyright (C) 2013-2022 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
@@ -19,6 +19,7 @@
package libnbd
import (
+ "fmt";
"testing"
)
@@ -37,6 +38,7 @@ func listmetaf(user_data int, name string) int {
}
func Test240OptListMeta(t *testing.T) {
+ /* Get into negotiating state. */
h, err := Create()
if err != nil {
t.Fatalf("could not create handle: %s", err)
@@ -143,4 +145,61 @@ func Test240OptListMeta(t *testing.T) {
if err != nil {
t.Fatalf("could not request opt_abort: %s", err)
}
+
+ /* Repeat but this time without structured replies. Deal gracefully
+ * with older servers that don't allow the attempt.
+ */
+ h, err = Create()
+ if err != nil {
+ t.Fatalf("could not create handle: %s", err)
+ }
+ defer h.Close()
+
+ err = h.SetOptMode(true)
+ if err != nil {
+ t.Fatalf("could not set opt mode: %s", err)
+ }
+
+ err = h.SetRequestStructuredReplies(false)
+ if err != nil {
+ t.Fatalf("could not set request structured replies: %s", err)
+ }
+
+ err = h.ConnectCommand([]string{
+ "nbdkit", "-s", "--exit-with-parent", "-v",
+ "memory", "size=1M",
+ })
+ if err != nil {
+ t.Fatalf("could not connect: %s", err)
+ }
+
+ bytes, err := h.StatsBytesSent()
+ if err != nil {
+ t.Fatalf("could not collect stats: %s", err)
+ }
+
+ count = 0
+ seen = false
+ r, err = h.OptListMetaContext(func(name string) int {
+ return listmetaf(42, name)
+ })
+ if err != nil {
+ bytes2, err2 := h.StatsBytesSent()
+ if err2 != nil {
+ t.Fatalf("could not collect stats: %s", err2)
+ }
+ if bytes2 <= bytes {
+ t.Fatalf("unexpected bytes sent after opt_list_meta_context")
+ }
+ fmt.Printf("ignoring failure from old server: %s", err)
+ } else if r < 1 || r != count || !seen {
+ t.Fatalf("unexpected count after opt_list_meta_context")
+ }
+
+ /* FIXME: Once nbd_opt_structured_reply exists, use it here and retry. */
+
+ err = h.OptAbort()
+ if err != nil {
+ t.Fatalf("could not request opt_abort: %s", err)
+ }
}
--
2.37.3