[PATCH libnbd] .gitignore: Ignore the new Go examples executables
by Nir Soffer
Fixes 1dbe4cf5fac3b9455462495fac230c21f1e44049.
Signed-off-by: Nir Soffer <nsoffer(a)redhat.com>
---
.gitignore | 2 ++
1 file changed, 2 insertions(+)
diff --git a/.gitignore b/.gitignore
index 3ecdceaf..395d137b 100644
--- a/.gitignore
+++ b/.gitignore
@@ -72,22 +72,24 @@ Makefile.in
/examples/copy-libev
/fuse/nbdfuse
/fuse/nbdfuse.1
/fuzzing/libnbd-fuzz-wrapper
/fuzzing/libnbd-libfuzzer-test
/fuzzing/sync_dir/
/fuzzing/testcase_dir/[0-f]*
/generator/generator
/generator/generator-cache.v1
/generator/stamp-generator
+/golang/examples/aio_copy/aio_copy
/golang/examples/get_size/get_size
/golang/examples/read_first_sector/read_first_sector
+/golang/examples/simple_copy/simple_copy
/golang/libnbd-golang.3
/html/*.?.html
/include/libnbd.h
/info/nbdinfo
/info/nbdinfo.1
/install-sh
/interop/dirty-bitmap
/interop/interop-nbd-server
/interop/interop-nbd-server-tls
/interop/interop-nbdkit
--
2.34.1
2 years, 9 months
[PATCH libnbd] Add .editorconfig file
by Nir Soffer
This project uses non standard 2 spaces indent in some parts (lib, copy,
info) and tabs in others (e.g. golang/*.go). This can be solved by local
configuration or adding metadata comments to files, but there is a
better solution - editorconfig[1].
Add .editorconfig file that use 2 spaces indent by default, and tabs for
the Go sources. This is not complete yet, but good enough for the areas
I usually touch.
People that do not use an editorconfig plugin are not affected by this
change. People that want to use it can install a plugin[2][3] and
improve the configuration as needed.
[1] https://editorconfig.org/
[2] https://github.com/editorconfig/editorconfig-vim#readme
[3] https://github.com/editorconfig/editorconfig-emacs#readme
Signed-off-by: Nir Soffer <nsoffer(a)redhat.com>
---
.editorconfig | 19 +++++++++++++++++++
Makefile.am | 1 +
2 files changed, 20 insertions(+)
create mode 100644 .editorconfig
diff --git a/.editorconfig b/.editorconfig
new file mode 100644
index 00000000..30e5f655
--- /dev/null
+++ b/.editorconfig
@@ -0,0 +1,19 @@
+# https://editorconfig.org/
+
+root = true
+
+[*]
+indent_style = space
+indent_size = 2
+insert_final_newline = true
+trim_trailing_whitespace = true
+charset = utf-8
+
+[{*.go,go.mod,go.sum}]
+# Match gofmt style
+indent_style = tab
+indent_size = 4
+
+[{Makefile,Makefile.in,Makefile.am}]
+# Make requires tabs.
+indent_style = tab
diff --git a/Makefile.am b/Makefile.am
index 45316b6d..a76ee379 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -14,20 +14,21 @@
# You should have received a copy of the GNU Lesser General Public
# License along with this library; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
include $(top_srcdir)/common-rules.mk
ACLOCAL_AMFLAGS = -I m4
EXTRA_DIST = \
.dir-locals.el \
+ .editorconfig \
.gitattributes \
.gitignore \
html/pod.css \
scripts/git.orderfile \
SECURITY \
$(NULL)
CLEANFILES += m4/*~
SUBDIRS = \
--
2.34.1
2 years, 9 months
[PATCH libnbd v2] golang: examples: Add simple_copy and aio_copy examples
by Nir Soffer
Show how to read entire image using the simple synchronous API and how
and the high performance asynchronous API.
For simplicity, the example do not use extents, do not sparsify the
image, and copy the image to only to stdout. This make it easy to
evaluate the Go bindings performance.
The aio_copy example includes an interesting ordering queue, ensuring
that asynchronous reads are written in the right order even if they
completed out of order. This allows using the fast asynchronous API even
when the output does not support seek, and may perform better even if
the output does support seek when using rotational disks.
The aio_copy example does not use AioBuffer in the normal way to avoid
unwanted copy when AioPread completes. Instead, we use a Go allocated
buffer, in the same way we pass a Go allocated buffer to libnbd in the
synchronous API. This usage is unsafe but required for getting decent
performance.
Testing show that we get better performance than nbdcopy with the
defaults, or the same performance if we tune nbdcopy to use the one
connection and 4 requests, and drop the data using null: output.
All tests use 6 GiB fully allocated zero image created with:
$ dd if=/dev/zero bs=1M count=6144 of=zero-6g.raw
The image is served using qemu-nbd:
$ qemu-nbd -r -t -e0 -k /tmp/nbd.sock --cache=none --aio=native -f raw zero-6g.raw
$ hyperfine "simple_copy/simple_copy $URL >/dev/null" \
"nbdcopy --synchronous --allocated $URL /dev/null" \
"nbdcopy --request-size $((2048*1024)) --synchronous --allocated $URL /dev/null"
Benchmark 1: simple_copy/simple_copy nbd+unix:///?socket=/tmp/nbd.sock >/dev/null
Time (mean ± σ): 3.210 s ± 0.065 s [User: 0.275 s, System: 0.836 s]
Range (min … max): 3.117 s … 3.298 s 10 runs
Benchmark 2: nbdcopy --synchronous --allocated nbd+unix:///?socket=/tmp/nbd.sock /dev/null
Time (mean ± σ): 4.469 s ± 0.019 s [User: 0.295 s, System: 0.948 s]
Range (min … max): 4.447 s … 4.510 s 10 runs
Benchmark 3: nbdcopy --request-size 2097152 --synchronous --allocated nbd+unix:///?socket=/tmp/nbd.sock /dev/null
Time (mean ± σ): 3.266 s ± 0.012 s [User: 0.216 s, System: 0.732 s]
Range (min … max): 3.244 s … 3.286 s 10 runs
Summary
'simple_copy/simple_copy nbd+unix:///?socket=/tmp/nbd.sock >/dev/null' ran
1.02 ± 0.02 times faster than 'nbdcopy --request-size 2097152 --synchronous --allocated nbd+unix:///?socket=/tmp/nbd.sock /dev/null'
1.39 ± 0.03 times faster than 'nbdcopy --synchronous --allocated nbd+unix:///?socket=/tmp/nbd.sock /dev/null'
$ hyperfine "aio_copy/aio_copy $URL >/dev/null" \
"nbdcopy --allocated $URL /dev/null" \
"nbdcopy --allocated $URL null:" \
"nbdcopy --connections 1 --requests 4 --allocated $URL null:"
Benchmark 1: aio_copy/aio_copy nbd+unix:///?socket=/tmp/nbd.sock >/dev/null
Time (mean ± σ): 2.013 s ± 0.035 s [User: 0.410 s, System: 0.877 s]
Range (min … max): 1.966 s … 2.060 s 10 runs
Benchmark 2: nbdcopy --allocated nbd+unix:///?socket=/tmp/nbd.sock /dev/null
Time (mean ± σ): 4.501 s ± 0.025 s [User: 0.287 s, System: 0.949 s]
Range (min … max): 4.449 s … 4.532 s 10 runs
Benchmark 3: nbdcopy --allocated nbd+unix:///?socket=/tmp/nbd.sock null:
Time (mean ± σ): 2.422 s ± 0.018 s [User: 0.520 s, System: 1.772 s]
Range (min … max): 2.404 s … 2.470 s 10 runs
Benchmark 4: nbdcopy --connections 1 --requests 4 --allocated nbd+unix:///?socket=/tmp/nbd.sock null:
Time (mean ± σ): 2.019 s ± 0.009 s [User: 0.270 s, System: 0.845 s]
Range (min … max): 2.008 s … 2.033 s 10 runs
Summary
'aio_copy/aio_copy nbd+unix:///?socket=/tmp/nbd.sock >/dev/null' ran
1.00 ± 0.02 times faster than 'nbdcopy --connections 1 --requests 4 --allocated nbd+unix:///?socket=/tmp/nbd.sock null:'
1.20 ± 0.02 times faster than 'nbdcopy --allocated nbd+unix:///?socket=/tmp/nbd.sock null:'
2.24 ± 0.04 times faster than 'nbdcopy --allocated nbd+unix:///?socket=/tmp/nbd.sock /dev/null'
Signed-off-by: Nir Soffer <nsoffer(a)redhat.com>
---
Chagnes in v2:
- Fix error handling: panic if aio_pread completion callback *error is
non-zero.
- Create AioPreadOptargs struct instead of pointer. This may save
unneeded allocation per read and is little bit simpler.
V1 was here:
https://listman.redhat.com/archives/libguestfs/2022-January/msg00165.html
golang/examples/Makefile.am | 26 ++-
golang/examples/aio_copy/aio_copy.go | 198 +++++++++++++++++++++
golang/examples/aio_copy/go.mod | 4 +
golang/examples/simple_copy/go.mod | 4 +
golang/examples/simple_copy/simple_copy.go | 93 ++++++++++
5 files changed, 323 insertions(+), 2 deletions(-)
create mode 100644 golang/examples/aio_copy/aio_copy.go
create mode 100644 golang/examples/aio_copy/go.mod
create mode 100644 golang/examples/simple_copy/go.mod
create mode 100644 golang/examples/simple_copy/simple_copy.go
diff --git a/golang/examples/Makefile.am b/golang/examples/Makefile.am
index bfeee245..90f9fbf8 100644
--- a/golang/examples/Makefile.am
+++ b/golang/examples/Makefile.am
@@ -16,27 +16,49 @@
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
include $(top_srcdir)/subdir-rules.mk
EXTRA_DIST = \
LICENSE-FOR-EXAMPLES \
get_size/go.mod \
get_size/get_size.go \
read_first_sector/go.mod \
read_first_sector/read_first_sector.go \
+ simple_copy/go.mod \
+ simple_copy/simple_copy.go \
+ aio_copy/go.mod \
+ aio_copy/aio_copy.go \
$(NULL)
if HAVE_GOLANG
-noinst_SCRIPTS = get_size/get_size read_first_sector/read_first_sector
+noinst_SCRIPTS = \
+ get_size/get_size \
+ read_first_sector/read_first_sector \
+ simple_copy/simple_copy \
+ aio_copy/aio_copy \
+ $(NULL)
get_size/get_size: get_size/get_size.go
cd get_size && \
$(abs_top_builddir)/run go build -o get_size
read_first_sector/read_first_sector: read_first_sector/read_first_sector.go
cd read_first_sector && \
$(abs_top_builddir)/run go build -o read_first_sector
+simple_copy/simple_copy: simple_copy/simple_copy.go
+ cd simple_copy && \
+ $(abs_top_builddir)/run go build -o simple_copy
+
+aio_copy/aio_copy: aio_copy/aio_copy.go
+ cd aio_copy && \
+ $(abs_top_builddir)/run go build -o aio_copy
+
endif HAVE_GOLANG
-CLEANFILES += get_size/get_size read_first_sector/read_first_sector
+CLEANFILES += \
+ get_size/get_size \
+ read_first_sector/read_first_sector \
+ simple_copy/simple_copy \
+ aio_copy/aio_copy \
+ $(NULL)
diff --git a/golang/examples/aio_copy/aio_copy.go b/golang/examples/aio_copy/aio_copy.go
new file mode 100644
index 00000000..b6f5def1
--- /dev/null
+++ b/golang/examples/aio_copy/aio_copy.go
@@ -0,0 +1,198 @@
+/* libnbd example
+ * Copyright (C) 2013-2022 Red Hat Inc.
+ * Examples are under a permissive BSD-like license. See also
+ * golang/examples/LICENSE-For-EXAMPLES
+ *
+ * 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.
+ */
+
+// Copy image from NBD URI to stdout.
+//
+// Example:
+//
+// ./aio_copy nbd+unix:///?socket=/tmp.nbd >/dev/null
+//
+package main
+
+import (
+ "container/list"
+ "flag"
+ "os"
+ "sync"
+ "syscall"
+ "unsafe"
+
+ "libguestfs.org/libnbd"
+)
+
+var (
+ // These options give best performance with fast NVMe drive.
+ requestSize = flag.Uint("request-size", 256*1024, "maximum request size in bytes")
+ requests = flag.Uint("requests", 4, "maximum number of requests in flight")
+
+ h *libnbd.Libnbd
+
+ // Keeping commands in a queue ensures commands are written in the right
+ // order, even if they complete out of order. This allows parallel reads
+ // with non-seekable output.
+ queue list.List
+
+ // Buffer pool allocating buffers as needed and reusing them.
+ bufPool = sync.Pool{
+ New: func() interface{} {
+ return make([]byte, *requestSize)
+ },
+ }
+)
+
+// command keeps state of single AioPread call while the read is handled by
+// libnbd, until the command reach the front of the queue and can be writen to
+// the output.
+type command struct {
+ buf []byte
+ length uint
+ ready bool
+}
+
+func main() {
+ flag.Parse()
+
+ var err error
+
+ h, err = libnbd.Create()
+ if err != nil {
+ panic(err)
+ }
+ defer h.Close()
+
+ err = h.ConnectUri(flag.Arg(0))
+ if err != nil {
+ panic(err)
+ }
+
+ size, err := h.GetSize()
+ if err != nil {
+ panic(err)
+ }
+
+ var offset uint64
+
+ for offset < size || queue.Len() > 0 {
+
+ for offset < size && inflightRequests() < *requests {
+ length := *requestSize
+ if size-offset < uint64(length) {
+ length = uint(size - offset)
+ }
+ startRead(offset, length)
+ offset += uint64(length)
+ }
+
+ waitForCompletion()
+
+ for readReady() {
+ finishRead()
+ }
+ }
+}
+
+func inflightRequests() uint {
+ n, err := h.AioInFlight()
+ if err != nil {
+ panic(err)
+ }
+ return n
+}
+
+func waitForCompletion() {
+ start := inflightRequests()
+
+ for {
+ _, err := h.Poll(-1)
+ if err != nil {
+ panic(err)
+ }
+
+ if inflightRequests() < start {
+ break // A read completed.
+ }
+ }
+}
+
+func startRead(offset uint64, length uint) {
+ buf := bufPool.Get().([]byte)
+
+ // Keep buffer in command so we can put it back into the pool when the
+ // command completes.
+ cmd := &command{buf: buf, length: length}
+
+ // Create aio buffer from pool buffer to avoid unneeded allocation for
+ // every read, and unneeded copy when completing the read.
+ abuf := libnbd.AioBuffer{P: unsafe.Pointer(&buf[0]), Size: length}
+
+ args := libnbd.AioPreadOptargs{
+ CompletionCallbackSet: true,
+ CompletionCallback: func(error *int) int {
+ if *error != 0 {
+ // This is not documented, but *error is errno value translated
+ // from the the NBD server error.
+ err := syscall.Errno(*error).Error()
+ panic(err)
+ }
+ cmd.ready = true
+ return 1
+ },
+ }
+
+ _, err := h.AioPread(abuf, offset, &args)
+ if err != nil {
+ panic(err)
+ }
+
+ queue.PushBack(cmd)
+}
+
+func readReady() bool {
+ return queue.Len() > 0 && queue.Front().Value.(*command).ready
+}
+
+func finishRead() {
+ e := queue.Front()
+ queue.Remove(e)
+
+ cmd := e.Value.(*command)
+ b := cmd.buf[:cmd.length]
+
+ _, err := os.Stdout.Write(b)
+ if err != nil {
+ panic(err)
+ }
+
+ bufPool.Put(cmd.buf)
+}
diff --git a/golang/examples/aio_copy/go.mod b/golang/examples/aio_copy/go.mod
new file mode 100644
index 00000000..074fabf7
--- /dev/null
+++ b/golang/examples/aio_copy/go.mod
@@ -0,0 +1,4 @@
+module main
+
+replace libguestfs.org/libnbd => ../../
+require libguestfs.org/libnbd v1.11.5
diff --git a/golang/examples/simple_copy/go.mod b/golang/examples/simple_copy/go.mod
new file mode 100644
index 00000000..074fabf7
--- /dev/null
+++ b/golang/examples/simple_copy/go.mod
@@ -0,0 +1,4 @@
+module main
+
+replace libguestfs.org/libnbd => ../../
+require libguestfs.org/libnbd v1.11.5
diff --git a/golang/examples/simple_copy/simple_copy.go b/golang/examples/simple_copy/simple_copy.go
new file mode 100644
index 00000000..e8fa1f76
--- /dev/null
+++ b/golang/examples/simple_copy/simple_copy.go
@@ -0,0 +1,93 @@
+/* libnbd example
+ * Copyright (C) 2013-2022 Red Hat Inc.
+ * Examples are under a permissive BSD-like license. See also
+ * golang/examples/LICENSE-For-EXAMPLES
+ *
+ * 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.
+ */
+
+// Copy image from NBD URI to stdout.
+//
+// Example:
+//
+// ./simple_copy nbd+unix:///?socket=/tmp.nbd >/dev/null
+//
+package main
+
+import (
+ "flag"
+ "os"
+
+ "libguestfs.org/libnbd"
+)
+
+var (
+ requestSize = flag.Uint("buffer-size", 2048*1024, "maximum request size in bytes")
+)
+
+func main() {
+ flag.Parse()
+
+ h, err := libnbd.Create()
+ if err != nil {
+ panic(err)
+ }
+ defer h.Close()
+
+ err = h.ConnectUri(flag.Arg(0))
+ if err != nil {
+ panic(err)
+ }
+
+ size, err := h.GetSize()
+ if err != nil {
+ panic(err)
+ }
+
+ buf := make([]byte, *requestSize)
+ var offset uint64
+
+ for offset < size {
+ if size-offset < uint64(len(buf)) {
+ buf = buf[:offset-size]
+ }
+
+ err = h.Pread(buf, offset, nil)
+ if err != nil {
+ panic(err)
+ }
+
+ _, err := os.Stdout.Write(buf)
+ if err != nil {
+ panic(err)
+ }
+
+ offset += uint64(len(buf))
+ }
+}
--
2.34.1
2 years, 9 months
[PATCH libnbd] golang: examples: Add simple_copy and aio_copy examples
by Nir Soffer
Show how to read entire image using the simple synchronous API and how
and the high performance asynchronous API.
For simplicity, the example do not use extents, do not sparsify the
image, and copy the image to only to stdout. This make it easy to
evaluate the Go bindings performance.
The aio_copy example includes an interesting ordering queue, ensuring
that asynchronous reads are written in the right order even if they
completed out of order. This allows using the fast asynchronous API even
when the output does not support seek, and may perform better even if
the output does support seek when using rotational disks.
The aio_copy example does not use AioBuffer in the normal way to avoid
unwanted copy when AioPread completes. Instead, we use a Go allocated
buffer, in the same way we pass a Go allocated buffer to libnbd in the
synchronous API. This usage is unsafe but required for getting decent
performance.
Testing show that we get better performance than nbdcopy with the
defaults, or the same performance if we tune nbdcopy to use the one
connection and 4 requests, and drop the data using null: output.
All tests use 6 GiB fully allocated zero image created with:
$ dd if=/dev/zero bs=1M count=6144 of=zero-6g.raw
The image is served using qemu-nbd:
$ qemu-nbd -r -t -e0 -k /tmp/nbd.sock --cache=none --aio=native -f raw zero-6g.raw
$ hyperfine "simple_copy/simple_copy $URL >/dev/null" \
"nbdcopy --synchronous --allocated $URL /dev/null" \
"nbdcopy --request-size $((2048*1024)) --synchronous --allocated $URL /dev/null"
Benchmark 1: simple_copy/simple_copy nbd+unix:///?socket=/tmp/nbd.sock >/dev/null
Time (mean ± σ): 3.210 s ± 0.065 s [User: 0.275 s, System: 0.836 s]
Range (min … max): 3.117 s … 3.298 s 10 runs
Benchmark 2: nbdcopy --synchronous --allocated nbd+unix:///?socket=/tmp/nbd.sock /dev/null
Time (mean ± σ): 4.469 s ± 0.019 s [User: 0.295 s, System: 0.948 s]
Range (min … max): 4.447 s … 4.510 s 10 runs
Benchmark 3: nbdcopy --request-size 2097152 --synchronous --allocated nbd+unix:///?socket=/tmp/nbd.sock /dev/null
Time (mean ± σ): 3.266 s ± 0.012 s [User: 0.216 s, System: 0.732 s]
Range (min … max): 3.244 s … 3.286 s 10 runs
Summary
'simple_copy/simple_copy nbd+unix:///?socket=/tmp/nbd.sock >/dev/null' ran
1.02 ± 0.02 times faster than 'nbdcopy --request-size 2097152 --synchronous --allocated nbd+unix:///?socket=/tmp/nbd.sock /dev/null'
1.39 ± 0.03 times faster than 'nbdcopy --synchronous --allocated nbd+unix:///?socket=/tmp/nbd.sock /dev/null'
$ hyperfine "aio_copy/aio_copy $URL >/dev/null" \
"nbdcopy --allocated $URL /dev/null" \
"nbdcopy --allocated $URL null:" \
"nbdcopy --connections 1 --requests 4 --allocated $URL null:"
Benchmark 1: aio_copy/aio_copy nbd+unix:///?socket=/tmp/nbd.sock >/dev/null
Time (mean ± σ): 2.013 s ± 0.035 s [User: 0.410 s, System: 0.877 s]
Range (min … max): 1.966 s … 2.060 s 10 runs
Benchmark 2: nbdcopy --allocated nbd+unix:///?socket=/tmp/nbd.sock /dev/null
Time (mean ± σ): 4.501 s ± 0.025 s [User: 0.287 s, System: 0.949 s]
Range (min … max): 4.449 s … 4.532 s 10 runs
Benchmark 3: nbdcopy --allocated nbd+unix:///?socket=/tmp/nbd.sock null:
Time (mean ± σ): 2.422 s ± 0.018 s [User: 0.520 s, System: 1.772 s]
Range (min … max): 2.404 s … 2.470 s 10 runs
Benchmark 4: nbdcopy --connections 1 --requests 4 --allocated nbd+unix:///?socket=/tmp/nbd.sock null:
Time (mean ± σ): 2.019 s ± 0.009 s [User: 0.270 s, System: 0.845 s]
Range (min … max): 2.008 s … 2.033 s 10 runs
Summary
'aio_copy/aio_copy nbd+unix:///?socket=/tmp/nbd.sock >/dev/null' ran
1.00 ± 0.02 times faster than 'nbdcopy --connections 1 --requests 4 --allocated nbd+unix:///?socket=/tmp/nbd.sock null:'
1.20 ± 0.02 times faster than 'nbdcopy --allocated nbd+unix:///?socket=/tmp/nbd.sock null:'
2.24 ± 0.04 times faster than 'nbdcopy --allocated nbd+unix:///?socket=/tmp/nbd.sock /dev/null'
Signed-off-by: Nir Soffer <nsoffer(a)redhat.com>
---
golang/examples/Makefile.am | 26 ++-
golang/examples/aio_copy/aio_copy.go | 191 +++++++++++++++++++++
golang/examples/aio_copy/go.mod | 4 +
golang/examples/simple_copy/go.mod | 4 +
golang/examples/simple_copy/simple_copy.go | 93 ++++++++++
5 files changed, 316 insertions(+), 2 deletions(-)
create mode 100644 golang/examples/aio_copy/aio_copy.go
create mode 100644 golang/examples/aio_copy/go.mod
create mode 100644 golang/examples/simple_copy/go.mod
create mode 100644 golang/examples/simple_copy/simple_copy.go
diff --git a/golang/examples/Makefile.am b/golang/examples/Makefile.am
index bfeee245..90f9fbf8 100644
--- a/golang/examples/Makefile.am
+++ b/golang/examples/Makefile.am
@@ -16,27 +16,49 @@
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
include $(top_srcdir)/subdir-rules.mk
EXTRA_DIST = \
LICENSE-FOR-EXAMPLES \
get_size/go.mod \
get_size/get_size.go \
read_first_sector/go.mod \
read_first_sector/read_first_sector.go \
+ simple_copy/go.mod \
+ simple_copy/simple_copy.go \
+ aio_copy/go.mod \
+ aio_copy/aio_copy.go \
$(NULL)
if HAVE_GOLANG
-noinst_SCRIPTS = get_size/get_size read_first_sector/read_first_sector
+noinst_SCRIPTS = \
+ get_size/get_size \
+ read_first_sector/read_first_sector \
+ simple_copy/simple_copy \
+ aio_copy/aio_copy \
+ $(NULL)
get_size/get_size: get_size/get_size.go
cd get_size && \
$(abs_top_builddir)/run go build -o get_size
read_first_sector/read_first_sector: read_first_sector/read_first_sector.go
cd read_first_sector && \
$(abs_top_builddir)/run go build -o read_first_sector
+simple_copy/simple_copy: simple_copy/simple_copy.go
+ cd simple_copy && \
+ $(abs_top_builddir)/run go build -o simple_copy
+
+aio_copy/aio_copy: aio_copy/aio_copy.go
+ cd aio_copy && \
+ $(abs_top_builddir)/run go build -o aio_copy
+
endif HAVE_GOLANG
-CLEANFILES += get_size/get_size read_first_sector/read_first_sector
+CLEANFILES += \
+ get_size/get_size \
+ read_first_sector/read_first_sector \
+ simple_copy/simple_copy \
+ aio_copy/aio_copy \
+ $(NULL)
diff --git a/golang/examples/aio_copy/aio_copy.go b/golang/examples/aio_copy/aio_copy.go
new file mode 100644
index 00000000..d2f0d33b
--- /dev/null
+++ b/golang/examples/aio_copy/aio_copy.go
@@ -0,0 +1,191 @@
+/* libnbd example
+ * Copyright (C) 2013-2022 Red Hat Inc.
+ * Examples are under a permissive BSD-like license. See also
+ * golang/examples/LICENSE-For-EXAMPLES
+ *
+ * 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.
+ */
+
+// Copy image from NBD URI to stdout.
+//
+// Example:
+//
+// ./aio_copy nbd+unix:///?socket=/tmp.nbd >/dev/null
+//
+package main
+
+import (
+ "container/list"
+ "flag"
+ "os"
+ "sync"
+ "unsafe"
+
+ "libguestfs.org/libnbd"
+)
+
+var (
+ // These options give best performance with fast NVMe drive.
+ requestSize = flag.Uint("request-size", 256*1024, "maximum request size in bytes")
+ requests = flag.Uint("requests", 4, "maximum number of requests in flight")
+
+ h *libnbd.Libnbd
+
+ // Keeping commands in a queue ensures commands are written in the right
+ // order, even if they complete out of order. This allows parallel reads
+ // with non-seekable output.
+ queue list.List
+
+ // Buffer pool allocating buffers as needed and reusing them.
+ bufPool = sync.Pool{
+ New: func() interface{} {
+ return make([]byte, *requestSize)
+ },
+ }
+)
+
+// command keeps state of single AioPread call while the read is handled by
+// libnbd, until the command reach the front of the queue and can be writen to
+// the output.
+type command struct {
+ buf []byte
+ length uint
+ ready bool
+}
+
+func main() {
+ flag.Parse()
+
+ var err error
+
+ h, err = libnbd.Create()
+ if err != nil {
+ panic(err)
+ }
+ defer h.Close()
+
+ err = h.ConnectUri(flag.Arg(0))
+ if err != nil {
+ panic(err)
+ }
+
+ size, err := h.GetSize()
+ if err != nil {
+ panic(err)
+ }
+
+ var offset uint64
+
+ for offset < size || queue.Len() > 0 {
+
+ for offset < size && inflightRequests() < *requests {
+ length := *requestSize
+ if size-offset < uint64(length) {
+ length = uint(size - offset)
+ }
+ startRead(offset, length)
+ offset += uint64(length)
+ }
+
+ waitForCompletion()
+
+ for readReady() {
+ finishRead()
+ }
+ }
+}
+
+func inflightRequests() uint {
+ n, err := h.AioInFlight()
+ if err != nil {
+ panic(err)
+ }
+ return n
+}
+
+func waitForCompletion() {
+ start := inflightRequests()
+
+ for {
+ _, err := h.Poll(-1)
+ if err != nil {
+ panic(err)
+ }
+
+ if inflightRequests() < start {
+ break // A read completed.
+ }
+ }
+}
+
+func startRead(offset uint64, length uint) {
+ buf := bufPool.Get().([]byte)
+
+ // Keep buffer in command so we can put it back into the pool when the
+ // command completes.
+ cmd := &command{buf: buf, length: length}
+
+ // Create aio buffer from pool buffer to avoid unneeded allocation for
+ // every read, and unneeded copy when completing the read.
+ abuf := libnbd.AioBuffer{P: unsafe.Pointer(&buf[0]), Size: length}
+
+ args := &libnbd.AioPreadOptargs{
+ CompletionCallbackSet: true,
+ CompletionCallback: func(error *int) int {
+ cmd.ready = true
+ return 1
+ },
+ }
+
+ _, err := h.AioPread(abuf, offset, args)
+ if err != nil {
+ panic(err)
+ }
+
+ queue.PushBack(cmd)
+}
+
+func readReady() bool {
+ return queue.Len() > 0 && queue.Front().Value.(*command).ready
+}
+
+func finishRead() {
+ e := queue.Front()
+ queue.Remove(e)
+
+ cmd := e.Value.(*command)
+ b := cmd.buf[:cmd.length]
+
+ _, err := os.Stdout.Write(b)
+ if err != nil {
+ panic(err)
+ }
+
+ bufPool.Put(cmd.buf)
+}
diff --git a/golang/examples/aio_copy/go.mod b/golang/examples/aio_copy/go.mod
new file mode 100644
index 00000000..074fabf7
--- /dev/null
+++ b/golang/examples/aio_copy/go.mod
@@ -0,0 +1,4 @@
+module main
+
+replace libguestfs.org/libnbd => ../../
+require libguestfs.org/libnbd v1.11.5
diff --git a/golang/examples/simple_copy/go.mod b/golang/examples/simple_copy/go.mod
new file mode 100644
index 00000000..074fabf7
--- /dev/null
+++ b/golang/examples/simple_copy/go.mod
@@ -0,0 +1,4 @@
+module main
+
+replace libguestfs.org/libnbd => ../../
+require libguestfs.org/libnbd v1.11.5
diff --git a/golang/examples/simple_copy/simple_copy.go b/golang/examples/simple_copy/simple_copy.go
new file mode 100644
index 00000000..e8fa1f76
--- /dev/null
+++ b/golang/examples/simple_copy/simple_copy.go
@@ -0,0 +1,93 @@
+/* libnbd example
+ * Copyright (C) 2013-2022 Red Hat Inc.
+ * Examples are under a permissive BSD-like license. See also
+ * golang/examples/LICENSE-For-EXAMPLES
+ *
+ * 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.
+ */
+
+// Copy image from NBD URI to stdout.
+//
+// Example:
+//
+// ./simple_copy nbd+unix:///?socket=/tmp.nbd >/dev/null
+//
+package main
+
+import (
+ "flag"
+ "os"
+
+ "libguestfs.org/libnbd"
+)
+
+var (
+ requestSize = flag.Uint("buffer-size", 2048*1024, "maximum request size in bytes")
+)
+
+func main() {
+ flag.Parse()
+
+ h, err := libnbd.Create()
+ if err != nil {
+ panic(err)
+ }
+ defer h.Close()
+
+ err = h.ConnectUri(flag.Arg(0))
+ if err != nil {
+ panic(err)
+ }
+
+ size, err := h.GetSize()
+ if err != nil {
+ panic(err)
+ }
+
+ buf := make([]byte, *requestSize)
+ var offset uint64
+
+ for offset < size {
+ if size-offset < uint64(len(buf)) {
+ buf = buf[:offset-size]
+ }
+
+ err = h.Pread(buf, offset, nil)
+ if err != nil {
+ panic(err)
+ }
+
+ _, err := os.Stdout.Write(buf)
+ if err != nil {
+ panic(err)
+ }
+
+ offset += uint64(len(buf))
+ }
+}
--
2.34.1
2 years, 10 months
[PATCH nbdkit] common/allocators: Always align mlock buffer
by Richard W.M. Jones
On platforms with 64K page sizes (RHEL + aarch64 or ppc64le) we have
hit a problem in test-memory-allocator-malloc-mlock.sh which appears
to be caused by:
1. The test requests a 2K mlocked buffer.
2. The allocated buffer straddles a page boundary sometimes.
3. Linux expands the mlock request to two pages, ie. the requested
size becomes 2 * 64K = 128K
4. The standard mlock limit on Linux is 64K so the mlock request is
rejected and the test fails.
Also POSIX requires mlock parameters to be page aligned (although
Linux does not require this).
This commit attempts to fix both problems by ensuring the mlock
request is always page aligned. On the separate mlock path we use the
usual vector reserve function to do overflow checks and so on, but
then we reallocate the buffer using posix_memalign (or valloc) so it
is page aligned before locking it. This requires a duplicate
allocation and memcpy but this path is not performance critical.
I extended the test mlock size from 2K to 8K to try to provoke the
original bug (if it still happens) more often.
Thanks: Laszlo Ersek
---
configure.ac | 4 +-
common/allocators/malloc.c | 89 ++++++++++++++++++---
tests/test-memory-allocator-malloc-mlock.sh | 16 +++-
3 files changed, 94 insertions(+), 15 deletions(-)
diff --git a/configure.ac b/configure.ac
index c426aec8..1ab85e3e 100644
--- a/configure.ac
+++ b/configure.ac
@@ -384,7 +384,9 @@ AC_CHECK_FUNCS([\
pipe \
pipe2 \
ppoll \
- posix_fadvise])
+ posix_fadvise \
+ posix_memalign \
+ valloc])
dnl Check for structs and members.
AC_CHECK_MEMBERS([struct dirent.d_type], [], [], [[#include <dirent.h>]])
diff --git a/common/allocators/malloc.c b/common/allocators/malloc.c
index eea44432..986a45c9 100644
--- a/common/allocators/malloc.c
+++ b/common/allocators/malloc.c
@@ -36,6 +36,9 @@
#include <stdlib.h>
#include <stdbool.h>
#include <string.h>
+#include <unistd.h>
+#include <errno.h>
+#include <assert.h>
#ifdef HAVE_SYS_MMAN_H
#include <sys/mman.h>
@@ -46,6 +49,7 @@
#include <nbdkit-plugin.h>
#include "cleanup.h"
+#include "rounding.h"
#include "vector.h"
#include "allocator.h"
@@ -81,13 +85,46 @@ m_alloc_free (struct allocator *a)
}
}
-/* Extend the underlying bytearray if needed. mlock if requested. */
+/* Extend the underlying bytearray if needed. */
static int
-extend (struct m_alloc *ma, uint64_t new_size)
+extend_without_mlock (struct m_alloc *ma, uint64_t new_size)
{
- ACQUIRE_WRLOCK_FOR_CURRENT_SCOPE (&ma->lock);
size_t old_size, n;
+ if (ma->ba.cap < new_size) {
+ old_size = ma->ba.cap;
+ n = new_size - ma->ba.cap;
+
+ if (bytearray_reserve (&ma->ba, n) == -1) {
+ nbdkit_error ("realloc: %m");
+ return -1;
+ }
+
+ /* Initialize the newly allocated memory to 0. */
+ memset (ma->ba.ptr + old_size, 0, n);
+ }
+
+ return 0;
+}
+
+#ifdef HAVE_MLOCK
+static int
+extend_with_mlock (struct m_alloc *ma, uint64_t new_size)
+{
+ size_t old_size, n;
+ void *p;
+#ifdef HAVE_POSIX_MEMALIGN
+ int r;
+#endif
+
+ long pagesize = sysconf (_SC_PAGE_SIZE);
+ assert (pagesize > 0);
+
+ /* POSIX requires both base and size of mlock to be page aligned,
+ * even though Linux does not.
+ */
+ new_size = ROUND_UP (new_size, pagesize);
+
if (ma->ba.cap < new_size) {
old_size = ma->ba.cap;
n = new_size - ma->ba.cap;
@@ -96,30 +133,62 @@ extend (struct m_alloc *ma, uint64_t new_size)
/* Since the memory might be moved by realloc, we must unlock the
* original array.
*/
- if (ma->use_mlock)
+ if (ma->use_mlock && ma->ba.ptr != NULL)
munlock (ma->ba.ptr, ma->ba.cap);
#endif
+ /* Call the normal vector reserve function so that it does the
+ * overflow checks.
+ */
if (bytearray_reserve (&ma->ba, n) == -1) {
nbdkit_error ("realloc: %m");
return -1;
}
+ assert (new_size <= ma->ba.cap);
/* Initialize the newly allocated memory to 0. */
memset (ma->ba.ptr + old_size, 0, n);
-#ifdef HAVE_MLOCK
- if (ma->use_mlock) {
- if (mlock (ma->ba.ptr, ma->ba.cap) == -1) {
- nbdkit_error ("allocator=malloc: mlock: %m");
- return -1;
- }
+ /* But now reallocate it as a page-aligned buffer so we can mlock it. */
+#ifdef HAVE_POSIX_MEMALIGN
+ if ((r = posix_memalign (&p, pagesize, ma->ba.cap)) != 0) {
+ errno = r;
+ nbdkit_error ("posix_memalign: %m");
+ return -1;
}
+#elif HAVE_VALLOC
+ p = valloc (ma->ba.cap);
+ if (p == NULL) {
+ nbdkit_error ("valloc: %m");
+ return -1;
+ }
+#else
+#error "this platform does not have posix_memalign or valloc"
#endif
+
+ memcpy (p, ma->ba.ptr, new_size);
+ if (mlock (ma->ba.ptr, new_size) == -1) {
+ nbdkit_error ("allocator=malloc: mlock: %m");
+ return -1;
+ }
}
return 0;
}
+#endif /* HAVE_MLOCK */
+
+static int
+extend (struct m_alloc *ma, uint64_t new_size)
+{
+ ACQUIRE_WRLOCK_FOR_CURRENT_SCOPE (&ma->lock);
+
+#ifdef HAVE_MLOCK
+ if (ma->use_mlock)
+ return extend_with_mlock (ma, new_size);
+#endif
+
+ return extend_without_mlock (ma, new_size);
+}
static int
m_alloc_set_size_hint (struct allocator *a, uint64_t size_hint)
diff --git a/tests/test-memory-allocator-malloc-mlock.sh b/tests/test-memory-allocator-malloc-mlock.sh
index 4ef92db6..983ef22b 100755
--- a/tests/test-memory-allocator-malloc-mlock.sh
+++ b/tests/test-memory-allocator-malloc-mlock.sh
@@ -46,9 +46,9 @@ if ! nbdkit memory --dump-plugin | grep -sq mlock=yes; then
fi
# ulimit -l is measured in kilobytes and so for this test must be at
-# least 2 (kilobytes) and we actually check it's a bit larger to allow
+# least 8 (kilobytes) and we actually check it's a bit larger to allow
# room for error. On Linux the default is usually 64.
-requires test `ulimit -l` -gt 8
+requires test `ulimit -l` -gt 16
sock=$(mktemp -u /tmp/nbdkit-test-sock.XXXXXX)
files="memory-allocator-malloc-mlock.pid $sock"
@@ -57,17 +57,21 @@ cleanup_fn rm -f $files
# Run nbdkit with memory plugin.
start_nbdkit -P memory-allocator-malloc-mlock.pid -U $sock \
- memory 2048 allocator=malloc,mlock=true
+ memory 8192 allocator=malloc,mlock=true
nbdsh --connect "nbd+unix://?socket=$sock" \
-c '
-# Write some stuff to the beginning, middle and end.
+# Write some stuff.
buf1 = b"1" * 512
h.pwrite(buf1, 0)
buf2 = b"2" * 512
h.pwrite(buf2, 1024)
buf3 = b"3" * 512
h.pwrite(buf3, 1536)
+buf4 = b"4" * 512
+h.pwrite(buf4, 4096)
+buf5 = b"5" * 1024
+h.pwrite(buf5, 8192-1024)
# Read it back.
buf11 = h.pread(len(buf1), 0)
@@ -76,4 +80,8 @@ buf22 = h.pread(len(buf2), 1024)
assert buf2 == buf22
buf33 = h.pread(len(buf3), 1536)
assert buf3 == buf33
+buf44 = h.pread(len(buf4), 4096)
+assert buf4 == buf44
+buf55 = h.pread(len(buf5), 8192-1024)
+assert buf5 == buf55
'
--
2.32.0
2 years, 10 months
[PATCH v2v 1/2] input: libvirt: Share a single connection to the source NBD server
by Richard W.M. Jones
When using virt-p2v from RHEL 7, it starts a very old qemu-nbd server
(probably 1.5.3) which required the --shared parameter to enable
sharing even in read-only mode. Since it doesn't pass this parameter
only a single connection at a time is allowed, and further connections
will deadlock. Note that later versions of qemu-nbd changed this so
that read-only connections permit sharing.
In modular virt-v2v we now use nbdkit-nbd-plugin to proxy the
connection to virt-p2v / qemu-nbd. When you connect to this multiple
times, as virt-v2v does, it will make multiple connections to the
backend qemu-nbd. This will cause a deadlock.
We can use the nbdkit-nbd-plugin shared=true flag to enable the plugin
to share a single connection to the backend between multiple nbdkit
clients.
---
input/input_libvirt.ml | 1 +
1 file changed, 1 insertion(+)
diff --git a/input/input_libvirt.ml b/input/input_libvirt.ml
index 33f61086ec..42050c157b 100644
--- a/input/input_libvirt.ml
+++ b/input/input_libvirt.ml
@@ -76,6 +76,7 @@ and libvirt_servers dir disks =
Nbdkit.add_filter cmd "cow";
Nbdkit.add_arg cmd "hostname" hostname;
Nbdkit.add_arg cmd "port" (string_of_int port);
+ Nbdkit.add_arg cmd "shared" "true";
let _, pid = Nbdkit.run_unix ~socket cmd in
(* --exit-with-parent should ensure nbdkit is cleaned
--
2.32.0
2 years, 10 months
[PATCH v2v 0/3] Fix test-v2v-trim.sh test
by Richard W.M. Jones
Final 3 patches needed to fix "make check-slow", specifically the
test-v2v-trim.sh test. This revealed a nasty bug in our
sparsification if the input disk format is qcow2.
Rich.
2 years, 10 months
[PATCH v2v 0/7] Mostly fix "make check-slow"
by Richard W.M. Jones
$ make && make check-slow
I hadn't run this set of tests in a while and they were all failing.
Luckily that was easy to fix (see patches 1 and 2).
However we need to change the mix of guests being tested, dropping
some old ones which are no longer testable, and adding some newer
ones.
There is one slow test which still fails (test-v2v-trim.sh) which I'm
still looking at.
Rich.
2 years, 10 months
[PATCH v2v] Restore message about setting up the input and output
by Richard W.M. Jones
Old virt-v2v would print a summary of the input and output options
before connecting to the input/output, looking something like this:
[ 0.2] Opening the source -i libvirt -ic [etc]
This gave reassurance that virt-v2v was doing something in the case
where the source was slow or unreachable. In particular if you use
-i libvirt with a vCenter URL, and the URL is wrong, libvirt hangs for
a few minutes without printing anything.
Modular virt-v2v rearranged things so the connecting phase was silent,
which meant that in the case above virt-v2v appeared to hang for a few
minutes printing nothing at all.
This change adds to_string functions to all the input and output
methods and uses them to print a message like:
[ 0.0] Setting up the source: -i libvirt -ic [etc]
Note the old "Opening the source" message now refers to the libguestfs
handle open and connection to the NBD source disk pipeline. The hang
still happens, but at least it's clearer what it's doing.
Typical full output looks like this:
$ virt-v2v -i disk /var/tmp/fedora-35.img -o disk -os /var/tmp/out
[ 0.0] Setting up the source: -i disk /var/tmp/fedora-35.img
[ 1.1] Opening the source
[ 5.9] Inspecting the source
[ 11.5] Checking for sufficient free disk space in the guest
[ 11.5] Converting Fedora Linux 35 (Thirty Five) to run on KVM
virt-v2v: warning: /files/boot/grub2/device.map/hd0 references unknown
device "vda". You may have to fix this entry manually after conversion.
virt-v2v: This guest has virtio drivers installed.
[ 57.4] Mapping filesystem data to avoid copying unused and blank areas
[ 61.0] Closing the overlay
[ 61.7] Assigning disks to buses
[ 61.7] Checking if the guest needs BIOS or UEFI to boot
[ 61.7] Setting up the destination: -o disk -os /var/tmp/out
[ 62.8] Copying disk 1/1
█ 100% [****************************************]
[ 81.7] Creating output metadata
[ 81.7] Finishing off
Fixes: https://bugzilla.redhat.com/show_bug.cgi?id=2041886
Reported-by: Xiaodai Wang
---
input/input.ml | 1 +
input/input.mli | 4 ++++
input/input_disk.ml | 2 ++
input/input_libvirt.ml | 10 ++++++++++
input/input_ova.ml | 2 ++
input/input_vcenter_https.ml | 9 +++++++++
input/input_vddk.ml | 9 +++++++++
input/input_vmx.ml | 2 ++
input/input_xen_ssh.ml | 9 +++++++++
output/output.ml | 1 +
output/output.mli | 4 ++++
output/output_disk.ml | 6 ++++++
output/output_glance.ml | 2 ++
output/output_json.ml | 6 ++++++
output/output_libvirt.ml | 6 ++++++
output/output_null.ml | 2 ++
output/output_openstack.ml | 2 ++
output/output_qemu.ml | 6 ++++++
output/output_rhv.ml | 2 ++
output/output_rhv_upload.ml | 9 +++++++++
output/output_vdsm.ml | 2 ++
v2v/v2v.ml | 4 ++++
22 files changed, 100 insertions(+)
diff --git a/input/input.ml b/input/input.ml
index 00474becb1..b1175fa32f 100644
--- a/input/input.ml
+++ b/input/input.ml
@@ -26,6 +26,7 @@ type options = {
}
module type INPUT = sig
+ val to_string : options -> string list -> string
val setup : string -> options -> string list -> Types.source
val query_input_options : unit -> unit
end
diff --git a/input/input.mli b/input/input.mli
index 4f899b1d1c..b61df3e9fa 100644
--- a/input/input.mli
+++ b/input/input.mli
@@ -26,6 +26,10 @@ type options = {
}
module type INPUT = sig
+ val to_string : options -> string list -> string
+ (** [to_string options args] converts the source to a printable
+ string (for messages). *)
+
val setup : string -> options -> string list -> Types.source
(** [setup dir options args]
diff --git a/input/input_disk.ml b/input/input_disk.ml
index bcdaf78c55..2b21950a2a 100644
--- a/input/input_disk.ml
+++ b/input/input_disk.ml
@@ -142,6 +142,8 @@ and detect_local_input_format { input_format } filenames =
get_format formats
module Disk = struct
+ let to_string options args = String.concat " " ("-i disk" :: args)
+
let setup dir options args =
disk_source dir options args
diff --git a/input/input_libvirt.ml b/input/input_libvirt.ml
index f20082c2fa..33f61086ec 100644
--- a/input/input_libvirt.ml
+++ b/input/input_libvirt.ml
@@ -129,6 +129,14 @@ and libvirt_xml_source _ args =
source, disks
module Libvirt_ = struct
+ let to_string options args =
+ let xs = "-i libvirt" :: args in
+ let xs =
+ match options.input_conn with
+ | Some ic -> ("-ic " ^ ic) :: xs
+ | None -> xs in
+ String.concat " " xs
+
let setup dir options args =
let source, data = libvirt_source options args in
libvirt_servers dir data;
@@ -139,6 +147,8 @@ module Libvirt_ = struct
end
module LibvirtXML = struct
+ let to_string options args = String.concat " " ("-i libvirtxml" :: args)
+
let setup dir options args =
let source, data = libvirt_xml_source options args in
libvirt_servers dir data;
diff --git a/input/input_ova.ml b/input/input_ova.ml
index 0115f771cb..19c22d550e 100644
--- a/input/input_ova.ml
+++ b/input/input_ova.ml
@@ -229,6 +229,8 @@ and error_missing_href href =
error (f_"-i ova: OVF references file ‘%s’ which was not found in the OVA archive") href
module OVA = struct
+ let to_string options args = String.concat " " ("-i ova" :: args)
+
let setup dir options args =
ova_source dir options args
diff --git a/input/input_vcenter_https.ml b/input/input_vcenter_https.ml
index 24ac927dda..bcefed16db 100644
--- a/input/input_vcenter_https.ml
+++ b/input/input_vcenter_https.ml
@@ -117,6 +117,15 @@ let rec vcenter_https_source dir options args =
source
module VCenterHTTPS = struct
+ let to_string options args =
+ let xs = args in
+ let xs =
+ match options.input_conn with
+ | Some ic -> ("-ic " ^ ic) :: xs
+ | None -> xs in
+ let xs = "-i libvirt" :: xs in
+ String.concat " " xs
+
let setup dir options args =
vcenter_https_source dir options args
diff --git a/input/input_vddk.ml b/input/input_vddk.ml
index 1cfb7f5ec3..b9a0b8bf5c 100644
--- a/input/input_vddk.ml
+++ b/input/input_vddk.ml
@@ -193,6 +193,15 @@ and vddk_source dir options args =
source
module VDDK = struct
+ let to_string options args =
+ let xs = "-it vddk" :: args in
+ let xs =
+ match options.input_conn with
+ | Some ic -> ("-ic " ^ ic) :: xs
+ | None -> xs in
+ let xs = "-i libvirt" :: xs in
+ String.concat " " xs
+
let setup dir options args =
vddk_source dir options args
diff --git a/input/input_vmx.ml b/input/input_vmx.ml
index 9065e8571d..6e8948f9d5 100644
--- a/input/input_vmx.ml
+++ b/input/input_vmx.ml
@@ -118,6 +118,8 @@ and absolute_path_from_other_file other_filename filename =
else (Filename.dirname (absolute_path other_filename)) // filename
module VMX = struct
+ let to_string options args = String.concat " " ("-i vmx" :: args)
+
let setup dir options args =
vmx_source dir options args
diff --git a/input/input_xen_ssh.ml b/input/input_xen_ssh.ml
index cb8b1f9124..f18ac5cf40 100644
--- a/input/input_xen_ssh.ml
+++ b/input/input_xen_ssh.ml
@@ -112,6 +112,15 @@ let rec xen_ssh_source dir options args =
source
module XenSSH = struct
+ let to_string options args =
+ let xs = args in
+ let xs =
+ match options.input_conn with
+ | Some ic -> ("-ic " ^ ic) :: xs
+ | None -> xs in
+ let xs = "-i libvirt" :: xs in
+ String.concat " " xs
+
let setup dir options args =
xen_ssh_source dir options args
diff --git a/output/output.ml b/output/output.ml
index 101da82a70..659d20ac31 100644
--- a/output/output.ml
+++ b/output/output.ml
@@ -38,6 +38,7 @@ type options = {
module type OUTPUT = sig
type t
+ val to_string : options -> string
val setup : string -> options -> Types.source -> t
val finalize : string -> options ->
Types.source -> Types.inspect -> Types.target_meta ->
diff --git a/output/output.mli b/output/output.mli
index 03d71daf67..ced2216106 100644
--- a/output/output.mli
+++ b/output/output.mli
@@ -30,6 +30,10 @@ module type OUTPUT = sig
type t
(** Opaque data used by the output mode. *)
+ val to_string : options -> string
+ (** [to_string options] converts the destination to a printable
+ string (for messages). *)
+
val setup : string -> options -> Types.source -> t
(** [setup dir options source]
diff --git a/output/output_disk.ml b/output/output_disk.ml
index eca3c727b7..386d031b46 100644
--- a/output/output_disk.ml
+++ b/output/output_disk.ml
@@ -96,6 +96,12 @@ and disk_finalize dir source inspect target_meta
module Disk = struct
type t = unit
+ let to_string options =
+ "-o disk" ^
+ match options.output_storage with
+ | Some os -> " -os " ^ os
+ | None -> ""
+
let setup dir options source =
if options.output_options <> [] then
error (f_"no -oo (output options) are allowed here");
diff --git a/output/output_glance.ml b/output/output_glance.ml
index 0d7838dd38..85cbe58ea5 100644
--- a/output/output_glance.ml
+++ b/output/output_glance.ml
@@ -122,6 +122,8 @@ and glance_finalize dir source inspect target_meta output_format tmpdir =
module Glance = struct
type t = string
+ let to_string options = "-o glance"
+
let setup dir options source =
if options.output_options <> [] then
error (f_"no -oo (output options) are allowed here");
diff --git a/output/output_json.ml b/output/output_json.ml
index bb0cdfeb5a..88fb4778d4 100644
--- a/output/output_json.ml
+++ b/output/output_json.ml
@@ -134,6 +134,12 @@ and json_path os output_name json_disks_pattern i =
module Json = struct
type t = unit
+ let to_string options =
+ "-o json" ^
+ match options.output_storage with
+ | Some os -> " -os " ^ os
+ | None -> ""
+
let setup dir options source =
let data = json_parse_options options in
let output_name = get_output_name options source in
diff --git a/output/output_libvirt.ml b/output/output_libvirt.ml
index 52c4540130..20333363b7 100644
--- a/output/output_libvirt.ml
+++ b/output/output_libvirt.ml
@@ -198,6 +198,12 @@ and target_features_of_capabilities_doc doc arch =
module Libvirt_ = struct
type t = string * string
+ let to_string options =
+ "-o libvirt" ^
+ match options.output_storage with
+ | Some os -> " -os " ^ os
+ | None -> ""
+
let setup dir options source =
if options.output_options <> [] then
error (f_"no -oo (output options) are allowed here");
diff --git a/output/output_null.ml b/output/output_null.ml
index 34fbd6e148..56fb7ec63c 100644
--- a/output/output_null.ml
+++ b/output/output_null.ml
@@ -76,6 +76,8 @@ and null_servers dir disks output_name =
module Null = struct
type t = unit
+ let to_string options = "-o null"
+
let setup dir options source =
if options.output_options <> [] then
error (f_"no -oo (output options) are allowed here");
diff --git a/output/output_openstack.ml b/output/output_openstack.ml
index 334a1fc2ae..1798548dc3 100644
--- a/output/output_openstack.ml
+++ b/output/output_openstack.ml
@@ -462,6 +462,8 @@ and iso_time =
module Openstack = struct
type t = string list
+ let to_string options = "-o openstack"
+
let setup dir options source =
let data = openstack_parse_options options in
let output_name = get_output_name options source in
diff --git a/output/output_qemu.ml b/output/output_qemu.ml
index 0aac1eba2b..3d5d67820e 100644
--- a/output/output_qemu.ml
+++ b/output/output_qemu.ml
@@ -315,6 +315,12 @@ and qemu_finalize dir source inspect target_meta
module QEMU = struct
type t = unit
+ let to_string options =
+ "-o qemu" ^
+ match options.output_storage with
+ | Some os -> " -os " ^ os
+ | None -> ""
+
let setup dir options source =
let data = qemu_parse_options options in
let output_name = get_output_name options source in
diff --git a/output/output_rhv.ml b/output/output_rhv.ml
index 6a67b7aa15..a386b9a58d 100644
--- a/output/output_rhv.ml
+++ b/output/output_rhv.ml
@@ -266,6 +266,8 @@ and check_storage_domain domain_class os mp =
module RHV = struct
type t = string * string * string * string list * string list * int64 list
+ let to_string options = "-o rhv"
+
let setup dir options source =
if options.output_options <> [] then
error (f_"no -oo (output options) are allowed here");
diff --git a/output/output_rhv_upload.ml b/output/output_rhv_upload.ml
index 91e7be45bc..4d8dc1c135 100644
--- a/output/output_rhv_upload.ml
+++ b/output/output_rhv_upload.ml
@@ -444,6 +444,15 @@ module RHVUpload = struct
JSON.field list * string option * string option *
string option * string
+ let to_string options =
+ "-o rhv-upload" ^
+ (match options.output_conn with
+ | Some oc -> " -oc " ^ oc
+ | None -> "") ^
+ (match options.output_storage with
+ | Some os -> " -os " ^ os
+ | None -> "")
+
let setup dir options source =
let data = rhv_upload_parse_options options in
let output_name = get_output_name options source in
diff --git a/output/output_vdsm.ml b/output/output_vdsm.ml
index ce0d5b5e4b..676ecf0010 100644
--- a/output/output_vdsm.ml
+++ b/output/output_vdsm.ml
@@ -212,6 +212,8 @@ and vdsm_finalize dir source inspect target_meta
module VDSM = struct
type t = string * string * int64 list
+ let to_string options = "-o vdsm"
+
let setup dir options source =
let data = vdsm_parse_options options in
let output_name = get_output_name options source in
diff --git a/v2v/v2v.ml b/v2v/v2v.ml
index 47e6e9371a..d74cc21f0a 100644
--- a/v2v/v2v.ml
+++ b/v2v/v2v.ml
@@ -532,6 +532,8 @@ read the man page virt-v2v(1).
} in
(* Start the input module (runs an NBD server in the background). *)
+ message (f_"Setting up the source: %s")
+ (Input_module.to_string input_options args);
let source = Input_module.setup tmpdir input_options args in
(* If --print-source then print the source metadata and exit. *)
@@ -548,6 +550,8 @@ read the man page virt-v2v(1).
unlink (tmpdir // "convert");
(* Start the output module (runs an NBD server in the background). *)
+ message (f_"Setting up the destination: %s")
+ (Output_module.to_string output_options);
let output_t = Output_module.setup tmpdir output_options source in
(* Debug the v2vdir. *)
--
2.32.0
2 years, 10 months