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 <[email protected]> --- 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 _______________________________________________ Libguestfs mailing list [email protected] https://www.redhat.com/mailman/listinfo/libguestfs
