Richard W.M. Jones
2018-Jan-22 16:12 UTC
[Libguestfs] [PATCH nbdkit] filters: Add caching filter.
This adds a cache filter, which works like the COW filter in reverse. For realistic use it needs a bit more work, especially to add limits on the size of the cache, a more sensible cache replacement policy, and perhaps some kind of background worker to write dirty blocks out. Rich.
Richard W.M. Jones
2018-Jan-22 16:12 UTC
[Libguestfs] [PATCH nbdkit] filters: Add caching filter.
--- TODO | 3 + configure.ac | 1 + docs/nbdkit.pod | 1 + filters/Makefile.am | 1 + filters/cache/Makefile.am | 62 ++++ filters/cache/cache.c | 525 ++++++++++++++++++++++++++++++++++ filters/cache/nbdkit-cache-filter.pod | 122 ++++++++ tests/Makefile.am | 3 + tests/test-cache.sh | 88 ++++++ 9 files changed, 806 insertions(+) diff --git a/TODO b/TODO index a00d4fb..b8ca74c 100644 --- a/TODO +++ b/TODO @@ -39,6 +39,9 @@ Suggestions for filters * injecting artificial errors for testing clients +* nbdkit-cache-filter needs limits on the maximum size of the cache; + it could also do with a sensible replacement policy, etc. + Composing nbdkit ---------------- diff --git a/configure.ac b/configure.ac index 1091d27..2e69688 100644 --- a/configure.ac +++ b/configure.ac @@ -513,6 +513,7 @@ AC_CONFIG_FILES([Makefile plugins/vddk/Makefile plugins/xz/Makefile filters/Makefile + filters/cache/Makefile filters/cow/Makefile filters/delay/Makefile filters/offset/Makefile diff --git a/docs/nbdkit.pod b/docs/nbdkit.pod index c7a1bd7..7674138 100644 --- a/docs/nbdkit.pod +++ b/docs/nbdkit.pod @@ -898,6 +898,7 @@ L<nbdkit-xz-plugin(1)>. Filters: +L<nbdkit-cache-filter(1)>, L<nbdkit-cow-filter(1)>, L<nbdkit-delay-filter(1)>, L<nbdkit-offset-filter(1)>, diff --git a/filters/Makefile.am b/filters/Makefile.am index 7e6fe5a..9996d77 100644 --- a/filters/Makefile.am +++ b/filters/Makefile.am @@ -31,6 +31,7 @@ # SUCH DAMAGE. SUBDIRS = \ + cache \ cow \ delay \ offset \ diff --git a/filters/cache/Makefile.am b/filters/cache/Makefile.am new file mode 100644 index 0000000..a371c37 --- /dev/null +++ b/filters/cache/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-cache-filter.pod + +CLEANFILES = *~ + +filterdir = $(libdir)/nbdkit/filters + +filter_LTLIBRARIES = nbdkit-cache-filter.la + +nbdkit_cache_filter_la_SOURCES = \ + cache.c \ + $(top_srcdir)/include/nbdkit-filter.h + +nbdkit_cache_filter_la_CPPFLAGS = \ + -I$(top_srcdir)/include +nbdkit_cache_filter_la_CFLAGS = \ + $(WARNINGS_CFLAGS) +nbdkit_cache_filter_la_LDFLAGS = \ + -module -avoid-version -shared + +if HAVE_POD2MAN + +man_MANS = nbdkit-cache-filter.1 +CLEANFILES += $(man_MANS) + +nbdkit-cache-filter.1: nbdkit-cache-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/cache/cache.c b/filters/cache/cache.c new file mode 100644 index 0000000..7410f0d --- /dev/null +++ b/filters/cache/cache.c @@ -0,0 +1,525 @@ +/* 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 <stdbool.h> +#include <string.h> +#include <inttypes.h> +#include <alloca.h> +#include <unistd.h> +#include <fcntl.h> +#include <errno.h> +#include <sys/types.h> +#include <sys/ioctl.h> + +#include <nbdkit-filter.h> + +#define DIV_ROUND_UP(n, d) (((n) + (d) - 1) / (d)) + +/* XXX See design comment in filters/cow/cow.c. */ +#define THREAD_MODEL NBDKIT_THREAD_MODEL_SERIALIZE_ALL_REQUESTS + +/* Size of a block in the cache. A 4K block size means that we need + * 64 MB of memory to store the bitmaps for a 1 TB underlying image. + */ +#define BLKSIZE 4096 + +/* The cache. */ +static int fd = -1; + +/* Bitmap. There are two bits per block which are updated as we read, + * write back or write through blocks. + * + * 00 = not in cache + * 01 = block cached and clean + * 10 = <unused> + * 11 = block cached and dirty + */ +static uint8_t *bitmap; + +/* Size of the bitmap in bytes. */ +static uint64_t bm_size; + +enum bm_entry { + BLOCK_NOT_CACHED = 0, + BLOCK_CLEAN = 1, + BLOCK_DIRTY = 3, +}; + +/* Caching mode. */ +static enum cache_mode { + CACHE_MODE_WRITEBACK, + CACHE_MODE_WRITETHROUGH, + CACHE_MODE_UNSAFE, +} cache_mode = CACHE_MODE_WRITEBACK; + +static void +cache_load (void) +{ + const char *tmpdir; + size_t len; + char *template; + + tmpdir = getenv ("TMPDIR"); + if (!tmpdir) + tmpdir = "/var/tmp"; + + nbdkit_debug ("cache: temporary directory for cache: %s", tmpdir); + + len = strlen (tmpdir) + 8; + template = alloca (len); + snprintf (template, len, "%s/XXXXXX", tmpdir); + + fd = mkostemp (template, O_CLOEXEC); + if (fd == -1) { + nbdkit_error ("mkostemp: %s: %m", tmpdir); + exit (EXIT_FAILURE); + } + + unlink (template); +} + +static void +cache_unload (void) +{ + if (fd >= 0) + close (fd); +} + +static int +cache_config (nbdkit_next_config *next, void *nxdata, + const char *key, const char *value) +{ + if (strcmp (key, "cache") == 0) { + if (strcmp (value, "writeback") == 0) { + cache_mode = CACHE_MODE_WRITEBACK; + return 0; + } + else if (strcmp (value, "writethrough") == 0) { + cache_mode = CACHE_MODE_WRITETHROUGH; + return 0; + } + else if (strcmp (value, "unsafe") == 0) { + cache_mode = CACHE_MODE_UNSAFE; + return 0; + } + else { + nbdkit_error ("invalid cache parameter, should be writeback|writethrough|unsafe"); + return -1; + } + } + else { + return next (nxdata, key, value); + } +} + +static void * +cache_open (nbdkit_next_open *next, void *nxdata, int readonly) +{ + /* We don't use the handle, so this just provides a non-NULL + * pointer that we can return. + */ + static int handle; + + if (next (nxdata, readonly) == -1) + return NULL; + + return &handle; +} + +/* Allocate or resize the cache file and bitmap. */ +static int +blk_set_size (uint64_t new_size) +{ + uint8_t *new_bm; + const size_t old_bm_size = bm_size; + size_t new_bm_size = DIV_ROUND_UP (new_size, BLKSIZE*8/2); + + new_bm = realloc (bitmap, new_bm_size); + if (new_bm == NULL) { + nbdkit_error ("realloc: %m"); + return -1; + } + bitmap = new_bm; + bm_size = new_bm_size; + if (old_bm_size < new_bm_size) + memset (&bitmap[old_bm_size], 0, new_bm_size-old_bm_size); + + nbdkit_debug ("cache: bitmap resized to %" PRIu64 " bytes", new_bm_size); + + if (ftruncate (fd, new_size) == -1) { + nbdkit_error ("ftruncate: %m"); + return -1; + } + + return 0; +} + +/* Get the file size and ensure the cache is the correct size. */ +static int64_t +cache_get_size (struct nbdkit_next_ops *next_ops, void *nxdata, + void *handle) +{ + int64_t size; + + size = next_ops->get_size (nxdata); + if (size == -1) + return -1; + + nbdkit_debug ("cache: underlying file size: %" PRIi64, size); + + if (blk_set_size (size)) + return -1; + + return size; +} + +/* Force an early call to cache_get_size, consequently truncating the + * cache to the correct size. + */ +static int +cache_prepare (struct nbdkit_next_ops *next_ops, void *nxdata, + void *handle) +{ + int64_t r; + + r = cache_get_size (next_ops, nxdata, handle); + return r >= 0 ? 0 : -1; +} + +/* Return true if the block is allocated. Consults the bitmap. */ +static enum bm_entry +blk_get_bitmap_entry (uint64_t blknum) +{ + uint64_t bm_offset = blknum / 4; + uint64_t bm_bit = 2 * (blknum % 4); + + if (bm_offset >= bm_size) { + nbdkit_debug ("blk_get_bitmap_entry: block number is out of range"); + return BLOCK_NOT_CACHED; + } + + return (bitmap[bm_offset] & (3 << bm_bit)) >> bm_bit; +} + +/* Update cache state of a block. */ +static void +blk_set_bitmap_entry (uint64_t blknum, enum bm_entry state) +{ + uint64_t bm_offset = blknum / 4; + uint64_t bm_bit = 2 * (blknum % 4); + + if (bm_offset >= bm_size) { + nbdkit_debug ("blk_set_bitmap_entry: block number is out of range"); + return; + } + + bitmap[bm_offset] |= (unsigned) state << bm_bit; +} + +/* These are the block operations. They always read or write a single + * whole block of size ‘blksize’. + */ +static int +blk_read (struct nbdkit_next_ops *next_ops, void *nxdata, + uint64_t blknum, uint8_t *block) +{ + off_t offset = blknum * BLKSIZE; + enum bm_entry state = blk_get_bitmap_entry (blknum); + + nbdkit_debug ("cache: blk_read block %" PRIu64 " (offset %" PRIu64 ") is %s", + blknum, (uint64_t) offset, + state == BLOCK_NOT_CACHED ? "not cached" : + state == BLOCK_CLEAN ? "clean" : + state == BLOCK_DIRTY ? "dirty" : + "unknown"); + + if (state == BLOCK_NOT_CACHED) /* Read underlying plugin. */ + return next_ops->pread (nxdata, block, BLKSIZE, offset); + else { /* Read cache. */ + if (pread (fd, block, BLKSIZE, offset) == -1) { + nbdkit_error ("pread: %m"); + return -1; + } + return 0; + } +} + +/* Write to the cache and the plugin. */ +static int +blk_writethrough (struct nbdkit_next_ops *next_ops, void *nxdata, + uint64_t blknum, const uint8_t *block) +{ + off_t offset = blknum * BLKSIZE; + + nbdkit_debug ("cache: blk_writethrough block %" PRIu64 + " (offset %" PRIu64 ")", + blknum, (uint64_t) offset); + + if (pwrite (fd, block, BLKSIZE, offset) == -1) { + nbdkit_error ("pwrite: %m"); + return -1; + } + + if (next_ops->pwrite (nxdata, block, BLKSIZE, offset) == -1) + return -1; + + blk_set_bitmap_entry (blknum, BLOCK_CLEAN); + + return 0; +} + +/* Write to the cache only. */ +static int +blk_writeback (struct nbdkit_next_ops *next_ops, void *nxdata, + uint64_t blknum, const uint8_t *block) +{ + off_t offset; + + if (cache_mode == CACHE_MODE_WRITETHROUGH) + return blk_writethrough (next_ops, nxdata, blknum, block); + + offset = blknum * BLKSIZE; + + nbdkit_debug ("cache: blk_writeback block %" PRIu64 + " (offset %" PRIu64 ")", + blknum, (uint64_t) offset); + + if (pwrite (fd, block, BLKSIZE, offset) == -1) { + nbdkit_error ("pwrite: %m"); + return -1; + } + blk_set_bitmap_entry (blknum, BLOCK_DIRTY); + + return 0; +} + +/* Read data. */ +static int +cache_pread (struct nbdkit_next_ops *next_ops, void *nxdata, + void *handle, void *buf, uint32_t count, uint64_t offset) +{ + uint8_t *block; + + block = malloc (BLKSIZE); + if (block == NULL) { + nbdkit_error ("malloc: %m"); + return -1; + } + + while (count > 0) { + uint64_t blknum, blkoffs, n; + + blknum = offset / BLKSIZE; /* block number */ + blkoffs = offset % BLKSIZE; /* offset within the block */ + n = BLKSIZE - blkoffs; /* max bytes we can read from this block */ + if (n > count) + n = count; + + if (blk_read (next_ops, nxdata, blknum, block) == -1) { + free (block); + return -1; + } + + memcpy (buf, &block[blkoffs], n); + + buf += n; + count -= n; + offset += n; + } + + free (block); + return 0; +} + +/* Write data. */ +static int +cache_pwrite (struct nbdkit_next_ops *next_ops, void *nxdata, + void *handle, const void *buf, uint32_t count, uint64_t offset) +{ + uint8_t *block; + + block = malloc (BLKSIZE); + if (block == NULL) { + nbdkit_error ("malloc: %m"); + return -1; + } + + while (count > 0) { + uint64_t blknum, blkoffs, n; + + blknum = offset / BLKSIZE; /* block number */ + blkoffs = offset % BLKSIZE; /* offset within the block */ + n = BLKSIZE - blkoffs; /* max bytes we can read from this block */ + if (n > count) + n = count; + + /* Do a read-modify-write operation on the current block. */ + if (blk_read (next_ops, nxdata, blknum, block) == -1) { + free (block); + return -1; + } + memcpy (&block[blkoffs], buf, n); + if (blk_writeback (next_ops, nxdata, blknum, block) == -1) { + free (block); + return -1; + } + + buf += n; + count -= n; + offset += n; + } + + free (block); + return 0; +} + +/* Zero data. */ +static int +cache_zero (struct nbdkit_next_ops *next_ops, void *nxdata, + void *handle, uint32_t count, uint64_t offset, int may_trim) +{ + uint8_t *block; + + block = malloc (BLKSIZE); + if (block == NULL) { + nbdkit_error ("malloc: %m"); + return -1; + } + + while (count > 0) { + uint64_t blknum, blkoffs, n; + + blknum = offset / BLKSIZE; /* block number */ + blkoffs = offset % BLKSIZE; /* offset within the block */ + n = BLKSIZE - blkoffs; /* max bytes we can read from this block */ + if (n > count) + n = count; + + if (blk_read (next_ops, nxdata, blknum, block) == -1) { + free (block); + return -1; + } + memset (&block[blkoffs], 0, n); + if (blk_writeback (next_ops, nxdata, blknum, block) == -1) { + free (block); + return -1; + } + + count -= n; + offset += n; + } + + free (block); + return 0; +} + +/* Flush: Go through all the dirty blocks, flushing them to disk. */ +static int +cache_flush (struct nbdkit_next_ops *next_ops, void *nxdata, void *handle) +{ + uint8_t *block = NULL; + uint64_t i, j; + uint64_t blknum; + enum bm_entry state; + unsigned errors = 0; + + if (cache_mode == CACHE_MODE_UNSAFE) + return 0; + + /* In theory if cache_mode == CACHE_MODE_WRITETHROUGH then there + * should be no dirty blocks. However we go through the cache here + * to be sure. Also we still need to issue the flush to the + * underlying storage. + */ + + for (i = 0; i < bm_size; ++i) { + if (bitmap[i] != 0) { + /* The bitmap stores information about 4 blocks per byte, + * therefore ... + */ + for (j = 0; j < 4; ++j) { + blknum = i*4+j; + state = blk_get_bitmap_entry (blknum); + if (state == BLOCK_DIRTY) { + /* Lazily allocate the bounce buffer. */ + if (!block) { + block = malloc (BLKSIZE); + if (block == NULL) { + nbdkit_error ("malloc: %m"); + return -1; + } + } + /* Perform a read + writethrough which will read from the + * cache and write it through to the underlying storage. + */ + if (blk_read (next_ops, nxdata, blknum, block) == -1 || + blk_writethrough (next_ops, nxdata, blknum, block)) { + nbdkit_error ("cache: flush of block %" PRIu64 " failed", blknum); + errors++; + } + } + } + } + } + + free (block); + + /* Now issue a flush request to the underlying storage. */ + if (next_ops->flush (nxdata) == -1) + errors++; + + return errors == 0 ? 0 : -1; +} + +static struct nbdkit_filter filter = { + .name = "cache", + .longname = "nbdkit caching filter", + .version = PACKAGE_VERSION, + .load = cache_load, + .unload = cache_unload, + .config = cache_config, + .open = cache_open, + .prepare = cache_prepare, + .get_size = cache_get_size, + .pread = cache_pread, + .pwrite = cache_pwrite, + .zero = cache_zero, + .flush = cache_flush, +}; + +NBDKIT_REGISTER_FILTER(filter) diff --git a/filters/cache/nbdkit-cache-filter.pod b/filters/cache/nbdkit-cache-filter.pod new file mode 100644 index 0000000..a59c54e --- /dev/null +++ b/filters/cache/nbdkit-cache-filter.pod @@ -0,0 +1,122 @@ +=encoding utf8 + +=head1 NAME + +nbdkit-cache-filter - nbdkit caching filter + +=head1 SYNOPSIS + + nbdkit --filter=cache plugin [cache=writeback|writethrough|unsafe] + [plugin-args...] + +=head1 DESCRIPTION + +C<nbdkit-cache-filter> is a filter that adds caching on top of a +plugin. This is useful if a plugin is slow or expensive to use, +because nbdkit will try to minimize requests to the plugin by caching +previous requests. + +Note that many NBD I<clients> are able to do caching, and because the +caching happens on the client side it will usually be more effective +than caching inside the server. This filter can be used if the client +does not have effective caching, or (with C<cache=unsafe>) to defeat +flush requests from the client (which is unsafe and can cause data +loss, as the name suggests). + +=head1 PARAMETERS + +=over 4 + +=item B<cache=writeback> + +Store writes in the cache. They are not written to the plugin unless +an explicit flush is done by the client. + +This is the default caching mode, and is safe if your client issues +flush requests correctly (which is true for modern Linux and other +well-written NBD clients). + +=item B<cache=writethrough> + +Always force writes through to the plugin. + +This makes the cache less effective, but is necessary if your client +does not issue correct flush requests. + +=item B<cache=unsafe> + +Ignore flush requests. Never write to the plugin unless the cache +grows too large. + +This is dangerous and can cause data loss, but this may be acceptable +if you only use it for testing or with data that you don't care about +or can cheaply reconstruct. + +=back + +=head1 ENVIRONMENT VARIABLES + +=over 4 + +=item C<TMPDIR> + +The cache is stored in a temporary file located in C</var/tmp> by +default. You can override this location by setting the C<TMPDIR> +environment variable before starting nbdkit. + +=back + +=head1 SEE ALSO + +L<nbdkit(1)>, +L<nbdkit-file-plugin(1)>, +L<nbdkit-filter(3)>, +L<qemu-img(1)>. + +=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/tests/Makefile.am b/tests/Makefile.am index b073f22..950f711 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -412,6 +412,9 @@ endif HAVE_RUBY #---------------------------------------------------------------------- # Tests of filters. +# cache filter test. +TESTS += test-cache.sh + # cow filter test. TESTS += test-cow.sh diff --git a/tests/test-cache.sh b/tests/test-cache.sh new file mode 100755 index 0000000..c950236 --- /dev/null +++ b/tests/test-cache.sh @@ -0,0 +1,88 @@ +#!/bin/bash - +# 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. + +set -e + +files="cache.img cache.sock cache.pid" +rm -f $files + +# Create an empty base image. +truncate -s 1G cache.img + +# Run nbdkit with the caching filter. +nbdkit -P cache.pid -U cache.sock --filter cache file file=cache.img + +# We may have to wait a short time for the pid file to appear. +for i in `seq 1 10`; do + if test -f cache.pid; then + break + fi + sleep 1 +done +if ! test -f cache.pid; then + echo "$0: PID file was not created" + exit 1 +fi + +pid="$(cat cache.pid)" + +# Kill the nbdkit process on exit. +cleanup () +{ + status=$? + + kill $pid + rm -f $files + + exit $status +} +trap cleanup INT QUIT TERM EXIT ERR + +# Open the overlay and perform some operations. +guestfish --format=raw -a 'nbd://?socket=cache.sock' <<'EOF' + run + part-disk /dev/sda gpt + mkfs ext4 /dev/sda1 + mount /dev/sda1 / + fill-dir / 10000 + fill-pattern "abcde" 5M /large + write /hello "hello, world" +EOF + +# Check the last files we created exist. +guestfish --ro -a cache.img -m /dev/sda1 <<'EOF' + cat /hello + cat /large | cat >/dev/null +EOF + +# The cleanup() function is called implicitly on exit. -- 2.15.1
Possibly Parallel Threads
- [PATCH nbdkit] common: Move shared bitmap code to a common library.
- [PATCH nbdkit v2] common: Move shared bitmap code to a common library.
- [PATCH nbdkit v3] common: Move shared bitmap code to a common library.
- [PATCH nbdkit] filters: Add copy-on-write filter.
- [PATCH nbdkit 0/9] cache: Implement cache-max-size and method of reclaiming space from the cache.