[Libguestfs] [PATCH 9/9] filters: Move rdelay/wdelay from file plugin to new delay filter.

Richard W.M. Jones
Wed, 17 Jan 2018 20:53:56 -0000

Previously the file plugin supported ‘rdelay’ and ‘wdelay’ parameters
for injecting delays (for testing) into read and write requests.  This
moves the functionality to a new delay filter so that it can be used
with any plugin.
---
 TODO                                  |   3 -
 configure.ac                          |   1 +
 filters/Makefile.am                   |   1 +
 filters/delay/Makefile.am             |  62 +++++++++++++
 filters/delay/delay.c                 | 161 ++++++++++++++++++++++++++++++++++
 filters/delay/nbdkit-delay-filter.pod |  88 +++++++++++++++++++
 plugins/file/file.c                   |  76 ++--------------
 plugins/file/nbdkit-file-plugin.pod   |  14 +--
 tests/test-parallel-file.sh           |   4 +-
 tests/test-parallel-nbd.sh            |   1 +
 10 files changed, 324 insertions(+), 87 deletions(-)

diff --git a/TODO b/TODO
index 8eda0d7..3ec45fd 100644
--- a/TODO
+++ b/TODO
@@ -37,9 +37,6 @@ directed to qemu-nbd for these use cases.
 Suggestions for filters
 -----------------------

-* adding artificial delays (see wdelay/rdelay options in the file
-  plugin)
-
 * injecting artificial errors for testing clients

 * copy-on-write, a popular feature in other servers
diff --git a/configure.ac b/configure.ac
index 4892dc4..9376a2e 100644
--- a/configure.ac
+++ b/configure.ac
@@ -513,6 +513,7 @@ AC_CONFIG_FILES([Makefile
                  plugins/vddk/Makefile
                  plugins/xz/Makefile
                  filters/Makefile
+                 filters/delay/Makefile
                  filters/offset/Makefile
                  src/Makefile
                  src/nbdkit.pc
diff --git a/filters/Makefile.am b/filters/Makefile.am
index 91fbe6c..d4aa6c0 100644
--- a/filters/Makefile.am
+++ b/filters/Makefile.am
@@ -31,4 +31,5 @@
 # SUCH DAMAGE.

 SUBDIRS = \
+	delay \
 	offset
diff --git a/filters/delay/Makefile.am b/filters/delay/Makefile.am
new file mode 100644
index 0000000..5b20b69
--- /dev/null
+++ b/filters/delay/Makefile.am
@@ -0,0 +1,62 @@
+# nbdkit
+# Copyright (C) 2018 Red Hat Inc.
+# All rights reserved.
+#
+# 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.
+
+EXTRA_DIST = nbdkit-delay-filter.pod
+
+CLEANFILES = *~
+
+filterdir = $(libdir)/nbdkit/filters
+
+filter_LTLIBRARIES = nbdkit-delay-filter.la
+
+nbdkit_delay_filter_la_SOURCES = \
+	delay.c \
+	$(top_srcdir)/include/nbdkit-filter.h
+
+nbdkit_delay_filter_la_CPPFLAGS = \
+	-I$(top_srcdir)/include
+nbdkit_delay_filter_la_CFLAGS = \
+	$(WARNINGS_CFLAGS)
+nbdkit_delay_filter_la_LDFLAGS = \
+	-module -avoid-version -shared
+
+if HAVE_POD2MAN
+
+man_MANS = nbdkit-delay-filter.1
+CLEANFILES += $(man_MANS)
+
+nbdkit-delay-filter.1: nbdkit-delay-filter.pod
+	$(POD2MAN) $(POD2MAN_ARGS) --section=1 --name=`basename $@ .1` $< $@.t
&& \
+	if grep 'POD ERROR' $@.t; then rm $@.t; exit 1; fi && \
+	mv $@.t $@
+
+endif
diff --git a/filters/delay/delay.c b/filters/delay/delay.c
new file mode 100644
index 0000000..c8d9ef2
--- /dev/null
+++ b/filters/delay/delay.c
@@ -0,0 +1,161 @@
+/* nbdkit
+ * Copyright (C) 2018 Red Hat Inc.
+ * All rights reserved.
+ *
+ * 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.
+ */
+
+#include <config.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <string.h>
+#include <time.h>
+
+#include <nbdkit-filter.h>
+
+static int rdelayms = 0;        /* read delay (milliseconds) */
+static int wdelayms = 0;        /* write delay (milliseconds) */
+
+static int
+parse_delay (const char *value)
+{
+  size_t len = strlen (value);
+  int r;
+
+  if (len > 2 && strcmp (&value[len-2], "ms") == 0) {
+    if (sscanf (value, "%d", &r) == 1)
+      return r;
+    else {
+      nbdkit_error ("cannot parse rdelay/wdelay milliseconds parameter:
%s",
+                    value);
+      return -1;
+    }
+  }
+  else {
+    if (sscanf (value, "%d", &r) == 1)
+      return r * 1000;
+    else {
+      nbdkit_error ("cannot parse rdelay/wdelay seconds parameter:
%s",
+                    value);
+      return -1;
+    }
+  }
+}
+
+static void
+delay (int ms)
+{
+  if (ms > 0) {
+    const struct timespec ts = {
+      .tv_sec = ms / 1000,
+      .tv_nsec = (ms * 1000000) % 1000000000
+    };
+    nanosleep (&ts, NULL);
+  }
+}
+
+static void
+read_delay (void)
+{
+  delay (rdelayms);
+}
+
+static void
+write_delay (void)
+{
+  delay (wdelayms);
+}
+
+/* Called for each key=value passed on the command line. */
+static int
+delay_config (nbdkit_next_config *next, void *nxdata,
+              const char *key, const char *value)
+{
+  if (strcmp (key, "rdelay") == 0) {
+    rdelayms = parse_delay (value);
+    if (rdelayms == -1)
+      return -1;
+    return 0;
+  }
+  else if (strcmp (key, "wdelay") == 0) {
+    wdelayms = parse_delay (value);
+    if (wdelayms == -1)
+      return -1;
+    return 0;
+  }
+  else
+    return next (nxdata, key, value);
+}
+
+#define delay_config_help \
+  "rdelay=<NN>[ms]                Read delay in
seconds/milliseconds.\n" \
+  "wdelay=<NN>[ms]                Write delay in
seconds/milliseconds." \
+
+/* Read data. */
+static int
+delay_pread (struct nbdkit_next *next, void *nxdata,
+             void *handle, void *buf, uint32_t count, uint64_t offset)
+{
+  read_delay ();
+  return next->pread (nxdata, buf, count, offset);
+}
+
+/* Write data. */
+static int
+delay_pwrite (struct nbdkit_next *next, void *nxdata,
+              void *handle,
+              const void *buf, uint32_t count, uint64_t offset)
+{
+  write_delay ();
+  return next->pwrite (nxdata, buf, count, offset);
+}
+
+/* Zero data. */
+static int
+delay_zero (struct nbdkit_next *next, void *nxdata,
+            void *handle, uint32_t count, uint64_t offset, int may_trim)
+{
+  write_delay ();
+  return next->zero (nxdata, count, offset, may_trim);
+}
+
+static struct nbdkit_filter filter = {
+  .name              = "delay",
+  .longname          = "nbdkit delay filter",
+  .version           = PACKAGE_VERSION,
+  .config            = delay_config,
+  .config_help       = delay_config_help,
+  .pread             = delay_pread,
+  .pwrite            = delay_pwrite,
+  .zero              = delay_zero,
+};
+
+NBDKIT_REGISTER_FILTER(filter)
diff --git a/filters/delay/nbdkit-delay-filter.pod
b/filters/delay/nbdkit-delay-filter.pod
new file mode 100644
index 0000000..10aba94
--- /dev/null
+++ b/filters/delay/nbdkit-delay-filter.pod
@@ -0,0 +1,88 @@
+=encoding utf8
+
+=head1 NAME
+
+nbdkit-delay-filter - nbdkit delay filter
+
+=head1 SYNOPSIS
+
+ nbdkit --filter=delay plugin rdelay=SECS wdelay=SECS [plugin-args...]
+
+ nbdkit --filter=delay plugin rdelay=<NN>ms wdelay=<NN>ms
[plugin-args...]
+
+=head1 DESCRIPTION
+
+C<nbdkit-delay-filter> is a filter that delays read and write requests
+by some seconds or milliseconds.  This is used to simulate a slow or
+remote server, or to test certain kinds of race conditions in Linux.
+
+=head1 PARAMETERS
+
+=over 4
+
+=item B<rdelay=SECS>
+
+=item B<rdelay=E<lt>NNE<gt>ms>
+
+The optional read delay in seconds or milliseconds.
+
+=item B<wdelay=SECS>
+
+=item B<wdelay=E<lt>NNE<gt>ms>
+
+The optional write delay in seconds or milliseconds.
+
+=back
+
+=head1 SEE ALSO
+
+L<nbdkit(1)>,
+L<nbdkit-filter(3)>.
+
+=head1 AUTHORS
+
+Richard W.M. Jones
+
+=head1 COPYRIGHT
+
+Copyright (C) 2018 Red Hat Inc.
+
+=head1 LICENSE
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+
+=over 4
+
+=item *
+
+Redistributions of source code must retain the above copyright
+notice, this list of conditions and the following disclaimer.
+
+=item *
+
+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.
+
+=item *
+
+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.
+
+=back
+
+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.
diff --git a/plugins/file/file.c b/plugins/file/file.c
index 4a91251..1fe4191 100644
--- a/plugins/file/file.c
+++ b/plugins/file/file.c
@@ -40,7 +40,6 @@
 #include <unistd.h>
 #include <sys/types.h>
 #include <sys/stat.h>
-#include <time.h>
 #include <errno.h>

 #include <nbdkit-plugin.h>
@@ -50,8 +49,6 @@
 #endif

 static char *filename = NULL;
-static int rdelayms = 0;        /* read delay (milliseconds) */
-static int wdelayms = 0;        /* write delay (milliseconds) */

 static void
 file_unload (void)
@@ -59,56 +56,6 @@ file_unload (void)
   free (filename);
 }

-static int
-parse_delay (const char *value)
-{
-  size_t len = strlen (value);
-  int r;
-
-  if (len > 2 && strcmp (&value[len-2], "ms") == 0) {
-    if (sscanf (value, "%d", &r) == 1)
-      return r;
-    else {
-      nbdkit_error ("cannot parse rdelay/wdelay milliseconds parameter:
%s",
-                    value);
-      return -1;
-    }
-  }
-  else {
-    if (sscanf (value, "%d", &r) == 1)
-      return r * 1000;
-    else {
-      nbdkit_error ("cannot parse rdelay/wdelay seconds parameter:
%s",
-                    value);
-      return -1;
-    }
-  }
-}
-
-static void
-delay (int ms)
-{
-  if (ms > 0) {
-    const struct timespec ts = {
-      .tv_sec = ms / 1000,
-      .tv_nsec = (ms * 1000000) % 1000000000
-    };
-    nanosleep (&ts, NULL);
-  }
-}
-
-static void
-read_delay (void)
-{
-  delay (rdelayms);
-}
-
-static void
-write_delay (void)
-{
-  delay (wdelayms);
-}
-
 /* Called for each key=value passed on the command line.  This plugin
  * only accepts file=<filename>, which is required.
  */
@@ -122,15 +69,10 @@ file_config (const char *key, const char *value)
     if (!filename)
       return -1;
   }
-  else if (strcmp (key, "rdelay") == 0) {
-    rdelayms = parse_delay (value);
-    if (rdelayms == -1)
-      return -1;
-  }
-  else if (strcmp (key, "wdelay") == 0) {
-    wdelayms = parse_delay (value);
-    if (wdelayms == -1)
-      return -1;
+  else if (strcmp (key, "rdelay") == 0 ||
+           strcmp (key, "wdelay") == 0) {
+    nbdkit_error ("add --filter=delay on the command line");
+    return -1;
   }
   else {
     nbdkit_error ("unknown parameter '%s'", key);
@@ -157,9 +99,7 @@ file_config_complete (void)
 }

 #define file_config_help \
-  "file=<FILENAME>     (required) The filename to serve.\n" \
-  "rdelay=<NN>[ms]                Read delay in
seconds/milliseconds.\n" \
-  "wdelay=<NN>[ms]                Write delay in
seconds/milliseconds." \
+  "file=<FILENAME>     (required) The filename to serve." \

 /* The per-connection handle. */
 struct handle {
@@ -241,8 +181,6 @@ file_pread (void *handle, void *buf, uint32_t count,
uint64_t offset)
 {
   struct handle *h = handle;

-  read_delay ();
-
   while (count > 0) {
     ssize_t r = pread (h->fd, buf, count, offset);
     if (r == -1) {
@@ -267,8 +205,6 @@ file_pwrite (void *handle, const void *buf, uint32_t count,
uint64_t offset)
 {
   struct handle *h = handle;

-  write_delay ();
-
   while (count > 0) {
     ssize_t r = pwrite (h->fd, buf, count, offset);
     if (r == -1) {
@@ -292,8 +228,6 @@ file_zero (void *handle, uint32_t count, uint64_t offset,
int may_trim)
 #endif
   int r = -1;

-  write_delay ();
-
 #ifdef FALLOC_FL_PUNCH_HOLE
   if (may_trim) {
     r = fallocate (h->fd, FALLOC_FL_PUNCH_HOLE | FALLOC_FL_KEEP_SIZE,
diff --git a/plugins/file/nbdkit-file-plugin.pod
b/plugins/file/nbdkit-file-plugin.pod
index a2d1e6a..016513a 100644
--- a/plugins/file/nbdkit-file-plugin.pod
+++ b/plugins/file/nbdkit-file-plugin.pod
@@ -31,21 +31,13 @@ This parameter is required.

 =item B<rdelay=E<lt>NNE<gt>ms>

-Delay reads for C<SECS> seconds or C<NN> milliseconds.
-This is used to simulate a slow or remote server, or to
-test certain kinds of race conditions in Linux.
-
-The default is no delay.
-
 =item B<wdelay=SECS>

 =item B<wdelay=E<lt>NNE<gt>ms>

-Delay writes for C<SECS> seconds or C<NN> milliseconds.
-This is used to simulate a slow or remote server, or to
-test certain kinds of race conditions in Linux.
-
-The default is no delay.
+These plugin parameters have been moved to the
+L<nbdkit-delay-filter(1)> filter.  Modify the command line to add
+I<--filter=delay> in order to use these parameters.

 =back

diff --git a/tests/test-parallel-file.sh b/tests/test-parallel-file.sh
index 79a60ac..b9e5f40 100755
--- a/tests/test-parallel-file.sh
+++ b/tests/test-parallel-file.sh
@@ -49,7 +49,7 @@ $QEMU_IO -f raw -c "aio_write -P 2 1 1" -c
"aio_read -P 1 0 1" -c aio_flush \
 trap 'rm -f test-parallel-file.out' 0 1 2 3 15

 # With --threads=1, the write should complete first because it was issued first
-nbdkit -v -t 1 -U - file file=file-data wdelay=2 rdelay=1 --run '
+nbdkit -v -t 1 -U - --filter delay file file=file-data wdelay=2 rdelay=1 --run
'
   $QEMU_IO -f raw -c "aio_write -P 2 1 1" -c "aio_read -P 1 0
1" -c aio_flush $nbd
 ' | tee test-parallel-file.out
 if test "$(grep '1/1' test-parallel-file.out)" != \
@@ -59,7 +59,7 @@ read 1/1 bytes at offset 0"; then
 fi

 # With default --threads, the faster read should complete first
-nbdkit -v -U - file file=file-data wdelay=2 rdelay=1 --run '
+nbdkit -v -U - --filter delay file file=file-data wdelay=2 rdelay=1 --run '
   $QEMU_IO -f raw -c "aio_write -P 2 1 1" -c "aio_read -P 1 0
1" -c aio_flush $nbd
 ' | tee test-parallel-file.out
 if test "$(grep '1/1' test-parallel-file.out)" != \
diff --git a/tests/test-parallel-nbd.sh b/tests/test-parallel-nbd.sh
index f8e5071..d87573d 100755
--- a/tests/test-parallel-nbd.sh
+++ b/tests/test-parallel-nbd.sh
@@ -54,6 +54,7 @@ trap 'rm -f test-parallel-nbd.out
test-parallel-nbd.sock' 0 1 2 3 15
 (
 rm -f test-parallel-nbd.sock
 nbdkit --exit-with-parent -v -U test-parallel-nbd.sock \
+  --filter delay \
   file file=file-data wdelay=2 rdelay=1 &

 # With --threads=1, the write should complete first because it was issued first
--
2.15.1