[Libguestfs] [nbdkit PATCH v3 13/14] python: Implement .list_exports and friends

Eric Blake eblake at redhat.com
Mon Sep 21 16:23:57 UTC 2020


Fairly straightforward.  .list_exports uses the same idiom as .extents
for returning an iterable of tuples, with additional support for a
bare name rather than a name/desc tuple.  .default_export and
.export_description are rather easy clients of nbdkit_strdup_intern.

Signed-off-by: Eric Blake <eblake at redhat.com>
---
 plugins/python/nbdkit-python-plugin.pod |  25 ++++
 tests/Makefile.am                       |   3 +
 plugins/python/python.c                 | 185 ++++++++++++++++++++----
 tests/test-python-export-list.sh        |  71 +++++++++
 tests/python-export-list.py             |  59 ++++++++
 5 files changed, 313 insertions(+), 30 deletions(-)
 create mode 100755 tests/test-python-export-list.sh
 create mode 100644 tests/python-export-list.py

diff --git a/plugins/python/nbdkit-python-plugin.pod b/plugins/python/nbdkit-python-plugin.pod
index 79aed288..a3bd520c 100644
--- a/plugins/python/nbdkit-python-plugin.pod
+++ b/plugins/python/nbdkit-python-plugin.pod
@@ -178,6 +178,23 @@ See L</Threads> below.

 There are no arguments or return value.

+=item C<list_exports>
+
+(Optional)
+
+ def list_exports(readonly, is_tls):
+   # return an iterable object (eg. list) of
+   # (name, description) tuples or bare names:
+   return [ (name1, desc1), name2, (name3, desc3), ... ]
+
+=item C<default_export>
+
+(Optional)
+
+ def default_export(readonly, is_tls):
+   # return a string
+   return "name"
+
 =item C<open>

 (Required)
@@ -199,6 +216,14 @@ After C<close> returns, the reference count of the handle is
 decremented in the C part, which usually means that the handle and its
 contents will be garbage collected.

+=item C<export_description>
+
+(Optional)
+
+ def export_description(h):
+   # return a string
+   return "description"
+
 =item C<get_size>

 (Required)
diff --git a/tests/Makefile.am b/tests/Makefile.am
index c2d668c1..390df711 100644
--- a/tests/Makefile.am
+++ b/tests/Makefile.am
@@ -1083,16 +1083,19 @@ TESTS += \
 	test-python.sh \
 	test-python-exception.sh \
 	test-python-export-name.sh \
+	test-python-export-list.sh \
 	test-python-thread-model.sh \
 	test-shebang-python.sh \
 	$(NULL)
 EXTRA_DIST += \
 	python-exception.py \
 	python-export-name.py \
+	python-export-list.py \
 	python-thread-model.py \
 	shebang.py \
 	test-python-exception.sh \
 	test-python-export-name.sh \
+	test-python-export-list.sh \
 	test-python-plugin.py \
 	test-python-thread-model.sh \
 	test-python.sh \
diff --git a/plugins/python/python.c b/plugins/python/python.c
index 27c5ede2..91cebc16 100644
--- a/plugins/python/python.c
+++ b/plugins/python/python.c
@@ -536,6 +536,99 @@ py_get_ready (void)
   return 0;
 }

+static int
+py_list_exports (int readonly, int is_tls, struct nbdkit_exports *exports)
+{
+  ACQUIRE_PYTHON_GIL_FOR_CURRENT_SCOPE;
+  PyObject *fn;
+  PyObject *r;
+  PyObject *iter, *t;
+
+  if (!callback_defined ("list_exports", &fn))
+    /* Do the same as the core server */
+    return nbdkit_use_default_export (exports);
+
+  PyErr_Clear ();
+
+  r = PyObject_CallFunction (fn, "ii", readonly, is_tls);
+  Py_DECREF (fn);
+  if (check_python_failure ("list_exports") == -1)
+    return -1;
+
+  iter = PyObject_GetIter (r);
+  if (iter == NULL) {
+    nbdkit_error ("list_exports method did not return "
+                  "something which is iterable");
+    Py_DECREF (r);
+    return -1;
+  }
+
+  while ((t = PyIter_Next (iter)) != NULL) {
+    PyObject *py_name, *py_desc;
+    CLEANUP_FREE char *name = NULL;
+    CLEANUP_FREE char *desc = NULL;
+
+    name = python_to_string (t);
+    if (!name) {
+      if (!PyTuple_Check (t) || PyTuple_Size (t) != 2) {
+        nbdkit_error ("list_exports method did not return an iterable of "
+                      "2-tuples");
+        Py_DECREF (iter);
+        Py_DECREF (r);
+        return -1;
+      }
+      py_name = PyTuple_GetItem (t, 0);
+      py_desc = PyTuple_GetItem (t, 1);
+      name = python_to_string (py_name);
+      desc = python_to_string (py_desc);
+      if (name == NULL || desc == NULL) {
+        nbdkit_error ("list_exports method did not return an iterable of "
+                      "string 2-tuples");
+        Py_DECREF (iter);
+        Py_DECREF (r);
+        return -1;
+      }
+    }
+    if (nbdkit_add_export (exports, name, desc) == -1) {
+      Py_DECREF (iter);
+      Py_DECREF (r);
+      return -1;
+    }
+  }
+
+  Py_DECREF (iter);
+  Py_DECREF (r);
+  return 0;
+}
+
+static const char *
+py_default_export (int readonly, int is_tls)
+{
+  ACQUIRE_PYTHON_GIL_FOR_CURRENT_SCOPE;
+  PyObject *fn;
+  PyObject *r;
+  CLEANUP_FREE char *name = NULL;
+
+  if (!callback_defined ("default_export", &fn))
+    return "";
+
+  PyErr_Clear ();
+
+  r = PyObject_CallFunction (fn, "ii", readonly, is_tls);
+  Py_DECREF (fn);
+  if (check_python_failure ("default_export") == -1)
+    return NULL;
+
+  name = python_to_string (r);
+  Py_DECREF (r);
+  if (!name) {
+    nbdkit_error ("default_export method did not return a string");
+    return NULL;
+  }
+
+  return nbdkit_strdup_intern (name);
+}
+
 struct handle {
   int can_zero;
   PyObject *py_h;
@@ -595,6 +688,35 @@ py_close (void *handle)
   free (h);
 }

+static const char *
+py_export_description (void *handle)
+{
+  ACQUIRE_PYTHON_GIL_FOR_CURRENT_SCOPE;
+  struct handle *h = handle;
+  PyObject *fn;
+  PyObject *r;
+  CLEANUP_FREE char *desc = NULL;
+
+  if (!callback_defined ("export_description", &fn))
+    return NULL;
+
+  PyErr_Clear ();
+
+  r = PyObject_CallFunctionObjArgs (fn, h->py_h, NULL);
+  Py_DECREF (fn);
+  if (check_python_failure ("export_description") == -1)
+    return NULL;
+
+  desc = python_to_string (r);
+  Py_DECREF (r);
+  if (!desc) {
+    nbdkit_error ("export_description method did not return a string");
+    return NULL;
+  }
+
+  return nbdkit_strdup_intern (desc);
+}
+
 static int64_t
 py_get_size (void *handle)
 {
@@ -1120,42 +1242,45 @@ py_extents (void *handle, uint32_t count, uint64_t offset,
 #define THREAD_MODEL NBDKIT_THREAD_MODEL_PARALLEL

 static struct nbdkit_plugin plugin = {
-  .name              = "python",
-  .version           = PACKAGE_VERSION,
+  .name               = "python",
+  .version            = PACKAGE_VERSION,

-  .load              = py_load,
-  .unload            = py_unload,
-  .dump_plugin       = py_dump_plugin,
+  .load               = py_load,
+  .unload             = py_unload,
+  .dump_plugin        = py_dump_plugin,

-  .config            = py_config,
-  .config_complete   = py_config_complete,
-  .config_help       = py_config_help,
+  .config             = py_config,
+  .config_complete    = py_config_complete,
+  .config_help        = py_config_help,

-  .thread_model      = py_thread_model,
-  .get_ready         = py_get_ready,
+  .thread_model       = py_thread_model,
+  .get_ready          = py_get_ready,
+  .list_exports       = py_list_exports,
+  .default_export     = py_default_export,

-  .open              = py_open,
-  .close             = py_close,
+  .open               = py_open,
+  .close              = py_close,

-  .get_size          = py_get_size,
-  .is_rotational     = py_is_rotational,
-  .can_multi_conn    = py_can_multi_conn,
-  .can_write         = py_can_write,
-  .can_flush         = py_can_flush,
-  .can_trim          = py_can_trim,
-  .can_zero          = py_can_zero,
-  .can_fast_zero     = py_can_fast_zero,
-  .can_fua           = py_can_fua,
-  .can_cache         = py_can_cache,
-  .can_extents       = py_can_extents,
+  .export_description = py_export_description,
+  .get_size           = py_get_size,
+  .is_rotational      = py_is_rotational,
+  .can_multi_conn     = py_can_multi_conn,
+  .can_write          = py_can_write,
+  .can_flush          = py_can_flush,
+  .can_trim           = py_can_trim,
+  .can_zero           = py_can_zero,
+  .can_fast_zero      = py_can_fast_zero,
+  .can_fua            = py_can_fua,
+  .can_cache          = py_can_cache,
+  .can_extents        = py_can_extents,

-  .pread             = py_pread,
-  .pwrite            = py_pwrite,
-  .flush             = py_flush,
-  .trim              = py_trim,
-  .zero              = py_zero,
-  .cache             = py_cache,
-  .extents           = py_extents,
+  .pread              = py_pread,
+  .pwrite             = py_pwrite,
+  .flush              = py_flush,
+  .trim               = py_trim,
+  .zero               = py_zero,
+  .cache              = py_cache,
+  .extents            = py_extents,
 };

 NBDKIT_REGISTER_PLUGIN (plugin)
diff --git a/tests/test-python-export-list.sh b/tests/test-python-export-list.sh
new file mode 100755
index 00000000..f8569979
--- /dev/null
+++ b/tests/test-python-export-list.sh
@@ -0,0 +1,71 @@
+#!/usr/bin/env bash
+# nbdkit
+# Copyright (C) 2018-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
+
+if test ! -d "$SRCDIR"; then
+    echo "$0: could not locate python-export-list.py"
+    exit 1
+fi
+
+# Python has proven very difficult to valgrind, therefore it is disabled.
+if [ "$NBDKIT_VALGRIND" = "1" ]; then
+    echo "$0: skipping Python test under valgrind."
+    exit 77
+fi
+
+requires nbdinfo --version
+requires nbdsh -c 'print(h.set_full_info)'
+requires jq --version
+
+pid=test-python-export-list.pid
+sock=`mktemp -u`
+out=test-python-export-list.out
+files="$pid $sock $out"
+rm -f $files
+cleanup_fn rm -f $files
+
+start_nbdkit -P $pid -U $sock python $SRCDIR/python-export-list.py
+
+nbdinfo --list --json nbd+unix://\?socket=$sock > $out
+cat $out
+# libnbd 1.4.0 differs from 1.4.1 on whether --list grabs descriptions
+result=$(jq -c '[.exports[] | [."export-name", .description]]' $out)
+test "$result" = '[["hello","world"],["name only",null]]' ||
+  test "$result" = '[["hello","world"],["name only","=name only="]]'
+
+nbdinfo --json nbd+unix://\?socket=$sock > $out
+cat $out
+diff -u <(jq -c '[.exports[] | [."export-name", .description]]' $out) \
+     <(printf %s\\n '[["hello","=hello="]]')
diff --git a/tests/python-export-list.py b/tests/python-export-list.py
new file mode 100644
index 00000000..6feb6782
--- /dev/null
+++ b/tests/python-export-list.py
@@ -0,0 +1,59 @@
+# nbdkit
+# Copyright (C) 2018-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.
+
+# Create an nbdkit sh plugin which reflects the export name back to
+# the caller in the virtual device data and size.
+
+import nbdkit
+
+
+def list_exports(readonly, is_tls):
+    return [("hello", "world"), "name only"]
+
+
+def default_export(readonly, is_tls):
+    return "hello"
+
+
+def open(readonly):
+    return nbdkit.export_name()
+
+
+def export_description(h):
+    return "=%s=" % h
+
+
+def get_size(h):
+    return 0
+
+
+def pread(h, count, offset):
+    pass
-- 
2.28.0




More information about the Libguestfs mailing list