Re: [Libguestfs] [nbdkit PATCH 1/2] retry: Add in retry support during .open

2023-01-27 Thread Eric Blake
On Fri, Jan 27, 2023 at 02:41:22PM -0600, Eric Blake wrote:
> Now that a filter can open a backend as many times as it wants,
> there's no longer a technical reason we can't retry .open.  However,
> adding retry logic here does mean we have to weaken an assert in the
> server backend code, since prepare can now be reached more than once.
> 
> Test coverage will be added in a separate patch, so that it becomes
> easy to swap patch order and see that the test fails without this
> patch.
> 
> See also https://bugzilla.redhat.com/show_bug.cgi?id=1841820
> ---

> +static void *
> +retry_open (nbdkit_next_open *next, nbdkit_context *nxdata,
> +int readonly, const char *exportname, int is_tls)
> +{
> +  struct retry_handle *h;
> +  struct retry_data data = {0};
> +
> +  h = malloc (sizeof *h);

Uninit memory...

> +  if (h == NULL) {
> +nbdkit_error ("malloc: %m");
> +return NULL;
> +  }
> +
> +  h->readonly = readonly;
> +  h->exportname = strdup (exportname);
> +  h->context = nxdata;
> +  if (h->exportname == NULL) {
> +nbdkit_error ("strdup: %m");
> +free (h);
> +return NULL;
> +  }
> +  h->reopens = 0;
> +
> +  if (next (nxdata, readonly, exportname) != -1)
> +h->open = true;
> +  else {

and h->open has not been assigned if we get here...

> +/* Careful - our .open must not return a handle unless do_retry()
> + * works, as the caller's next action will be calling .get_size
> + * and similar probe functions which we do not bother to wire up
> + * into retry logic because they only need to be used right after
> + * connecting.
> + */
> +nbdkit_next *next_handle = NULL;
> +int err = ESHUTDOWN;
> +
> +while (! h->open && do_retry (h, &data, &next_handle, "open", &err))

...even though I assumed it was zero-initialized here.  Will fix.

-- 
Eric Blake, Principal Software Engineer
Red Hat, Inc.   +1-919-301-3266
Virtualization:  qemu.org | libvirt.org
___
Libguestfs mailing list
Libguestfs@redhat.com
https://listman.redhat.com/mailman/listinfo/libguestfs



[Libguestfs] [nbdkit PATCH 2/2] retry: Test previous patch

2023-01-27 Thread Eric Blake
Separate commit to allow rearranging patches to demonstrate that open
can now be retried.
---
 tests/Makefile.am|  2 +
 tests/test-retry-open.sh | 85 
 2 files changed, 87 insertions(+)
 create mode 100755 tests/test-retry-open.sh

diff --git a/tests/Makefile.am b/tests/Makefile.am
index e5396b5f..713ccca7 100644
--- a/tests/Makefile.am
+++ b/tests/Makefile.am
@@ -1807,6 +1807,7 @@ TESTS += \
test-retry-size.sh \
test-retry-reopen-fail.sh \
test-retry-zero-flags.sh \
+   test-retry-open.sh \
$(NULL)
 EXTRA_DIST += \
test-retry.sh \
@@ -1815,6 +1816,7 @@ EXTRA_DIST += \
test-retry-size.sh \
test-retry-reopen-fail.sh \
test-retry-zero-flags.sh \
+   test-retry-open.sh \
$(NULL)

 # retry-request filter test.
diff --git a/tests/test-retry-open.sh b/tests/test-retry-open.sh
new file mode 100755
index ..468a03e1
--- /dev/null
+++ b/tests/test-retry-open.sh
@@ -0,0 +1,85 @@
+#!/usr/bin/env bash
+# nbdkit
+# Copyright (C) 2018-2023 Red Hat Inc.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
+#
+# * Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+#
+# * Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# * Neither the name of Red Hat nor the names of its contributors may be
+# used to endorse or promote products derived from this software without
+# specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY RED HAT AND CONTRIBUTORS ''AS IS'' AND
+# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+# THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
+# PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL RED HAT OR
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
+# USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+# OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+# SUCH DAMAGE.
+
+source ./functions.sh
+set -e
+set -x
+
+requires_plugin sh
+requires qemu-io --version
+
+files="retry-open-count"
+rm -f $files
+cleanup_fn rm -f $files
+
+echo 0 > retry-open-count
+start_t=$SECONDS
+
+# Create a custom plugin which will test retrying open.
+nbdkit -v -U - \
+   sh - \
+   --filter=retry retry-delay=1 \
+   --run 'qemu-io -f raw -c "r -P0 0 512" $nbd || :' <<'EOF'
+#!/usr/bin/env bash
+case "$1" in
+open)
+# Count how many times the connection is (re-)opened.
+read i < retry-open-count
+echo $((i+1)) > retry-open-count
+if test $i -lt 1; then
+  echo EIO >&2; exit 1
+fi
+;;
+get_size) echo 512 ;;
+pread) dd if=/dev/zero count=$3 iflag=count_bytes ;;
+*) exit 2 ;;
+esac
+EOF
+
+# In this test we should see 1 failure:
+# first open FAILS
+# retry and wait 1 second
+# second open PASSES
+
+# The minimum time for the test should be 1 second.
+end_t=$SECONDS
+if [ $((end_t - start_t)) -lt 1 ]; then
+echo "$0: test ran too quickly"
+exit 1
+fi
+
+# Check the handle was opened 2 times (first open + one reopen).
+read open_count < retry-open-count
+if [ $open_count -ne 2 ]; then
+echo "$0: open-count ($open_count) != 2"
+exit 1
+fi
-- 
2.39.1

___
Libguestfs mailing list
Libguestfs@redhat.com
https://listman.redhat.com/mailman/listinfo/libguestfs



[Libguestfs] [nbdkit PATCH 1/2] retry: Add in retry support during .open

2023-01-27 Thread Eric Blake
Now that a filter can open a backend as many times as it wants,
there's no longer a technical reason we can't retry .open.  However,
adding retry logic here does mean we have to weaken an assert in the
server backend code, since prepare can now be reached more than once.

Test coverage will be added in a separate patch, so that it becomes
easy to swap patch order and see that the test fails without this
patch.

See also https://bugzilla.redhat.com/show_bug.cgi?id=1841820
---
 server/backend.c  |  7 +++-
 filters/retry/retry.c | 98 +--
 2 files changed, 63 insertions(+), 42 deletions(-)

diff --git a/server/backend.c b/server/backend.c
index 3bbba601..45889ea9 100644
--- a/server/backend.c
+++ b/server/backend.c
@@ -1,5 +1,5 @@
 /* nbdkit
- * Copyright (C) 2013-2021 Red Hat Inc.
+ * Copyright (C) 2013-2023 Red Hat Inc.
  *
  * Redistribution and use in source and binary forms, with or without
  * modification, are permitted provided that the following conditions are
@@ -312,7 +312,10 @@ backend_prepare (struct context *c)
   struct backend *b = c->b;

   assert (c->handle);
-  assert ((c->state & (HANDLE_OPEN | HANDLE_CONNECTED)) == HANDLE_OPEN);
+  assert (c->state & HANDLE_OPEN);
+
+  if (c->state & HANDLE_CONNECTED)
+return 0;

   /* Call these in order starting from the filter closest to the
* plugin, similar to typical .open order.  But remember that
diff --git a/filters/retry/retry.c b/filters/retry/retry.c
index fb1d0526..7abf74c4 100644
--- a/filters/retry/retry.c
+++ b/filters/retry/retry.c
@@ -1,5 +1,5 @@
 /* nbdkit
- * Copyright (C) 2019-2021 Red Hat Inc.
+ * Copyright (C) 2019-2023 Red Hat Inc.
  *
  * Redistribution and use in source and binary forms, with or without
  * modification, are permitted provided that the following conditions are
@@ -113,45 +113,6 @@ struct retry_handle {
   bool open;
 };

-static void *
-retry_open (nbdkit_next_open *next, nbdkit_context *nxdata,
-int readonly, const char *exportname, int is_tls)
-{
-  struct retry_handle *h;
-
-  if (next (nxdata, readonly, exportname) == -1)
-return NULL;
-
-  h = malloc (sizeof *h);
-  if (h == NULL) {
-nbdkit_error ("malloc: %m");
-return NULL;
-  }
-
-  h->readonly = readonly;
-  h->exportname = strdup (exportname);
-  h->context = nxdata;
-  if (h->exportname == NULL) {
-nbdkit_error ("strdup: %m");
-free (h);
-return NULL;
-  }
-  h->reopens = 0;
-  h->open = true;
-
-  return h;
-}
-
-static void
-retry_close (void *handle)
-{
-  struct retry_handle *h = handle;
-
-  nbdkit_debug ("reopens needed: %u", h->reopens);
-  free (h->exportname);
-  free (h);
-}
-
 /* This function encapsulates the common retry logic used across all
  * data commands.  If it returns true then the data command will retry
  * the operation.  ‘struct retry_data’ is stack data saved between
@@ -247,6 +208,63 @@ do_retry (struct retry_handle *h, struct retry_data *data,
   return true;
 }

+static void *
+retry_open (nbdkit_next_open *next, nbdkit_context *nxdata,
+int readonly, const char *exportname, int is_tls)
+{
+  struct retry_handle *h;
+  struct retry_data data = {0};
+
+  h = malloc (sizeof *h);
+  if (h == NULL) {
+nbdkit_error ("malloc: %m");
+return NULL;
+  }
+
+  h->readonly = readonly;
+  h->exportname = strdup (exportname);
+  h->context = nxdata;
+  if (h->exportname == NULL) {
+nbdkit_error ("strdup: %m");
+free (h);
+return NULL;
+  }
+  h->reopens = 0;
+
+  if (next (nxdata, readonly, exportname) != -1)
+h->open = true;
+  else {
+/* Careful - our .open must not return a handle unless do_retry()
+ * works, as the caller's next action will be calling .get_size
+ * and similar probe functions which we do not bother to wire up
+ * into retry logic because they only need to be used right after
+ * connecting.
+ */
+nbdkit_next *next_handle = NULL;
+int err = ESHUTDOWN;
+
+while (! h->open && do_retry (h, &data, &next_handle, "open", &err))
+  ;
+
+if (! h->open) {
+  free (h->exportname);
+  free (h);
+  return NULL;
+}
+  }
+  return h;
+}
+
+static void
+retry_close (void *handle)
+{
+  struct retry_handle *h = handle;
+
+  nbdkit_debug ("reopens needed: %u", h->reopens);
+  free (h->exportname);
+  free (h);
+}
+
 static int
 retry_pread (nbdkit_next *next,
  void *handle, void *buf, uint32_t count, uint64_t offset,
-- 
2.39.1

___
Libguestfs mailing list
Libguestfs@redhat.com
https://listman.redhat.com/mailman/listinfo/libguestfs


[Libguestfs] [nbdkit PATCH 0/2] retry: add support for retrying .open

2023-01-27 Thread Eric Blake
In https://bugzilla.redhat.com/show_bug.cgi?id=1841820, it was pointed
out that the retry filter not retrying .open means that an ssh
connection (such as in a vmx+ssh v2v conversion) fails when the ssh
connection itself cannot be retried.  A year ago, this was an inherent
limitation of our retry implementation; but in the meantime, my work
to allow filters to open independent backends has made it feasible to
implement.

Eric Blake (2):
  retry: Add in retry support during .open
  retry: Test previous patch

 tests/Makefile.am|  2 +
 server/backend.c |  7 ++-
 filters/retry/retry.c| 98 
 tests/test-retry-open.sh | 85 ++
 4 files changed, 150 insertions(+), 42 deletions(-)
 create mode 100755 tests/test-retry-open.sh

-- 
2.39.1

___
Libguestfs mailing list
Libguestfs@redhat.com
https://listman.redhat.com/mailman/listinfo/libguestfs



Re: [Libguestfs] Proposal for tools to inspect drivers and inject Windows virtio drivers

2023-01-27 Thread Richard W.M. Jones
On Thu, Jan 19, 2023 at 06:49:32PM +, Richard W.M. Jones wrote:
> On Sat, Dec 10, 2022 at 09:45:11PM +, Richard W.M. Jones wrote:
> > (a) Inspecting a virtual machine disk image to find out what virtual
> > devices it needs (ie. what drivers are installed in the guest).
> 
> guestfs-tools 1.49.9 contains a new tool called 'virt-drivers' which
> has this functionality extracted from virt-v2v.  It's Linux only at
> the moment (like virt-v2v).  Implementing this for Windows is TBD.

The virt-drivers tool supports listing Windows drivers starting with
1.49.10, so for example you can use this to tell if a Windows guest
has virtio drivers (or VMware drivers) suitable for the target
hypervisor.

Rich.

-- 
Richard Jones, Virtualization Group, Red Hat http://people.redhat.com/~rjones
Read my programming and virtualization blog: http://rwmj.wordpress.com
nbdkit - Flexible, fast NBD server with plugins
https://gitlab.com/nbdkit/nbdkit
___
Libguestfs mailing list
Libguestfs@redhat.com
https://listman.redhat.com/mailman/listinfo/libguestfs



[Libguestfs] [PATCH v2v v2] -o rhv-upload: Improve error message for invalid or missing -os parameter

2023-01-27 Thread Richard W.M. Jones
For -o rhv-upload, the -os parameter specifies the storage domain.
Because the RHV API allows globs when searching for a domain, if you
used a parameter like -os 'data*' then this would confuse the Python
code, since it can glob to the name of a storage domain, but then
later fail when we try to exact match the storage domain we found.
The result of this was a confusing error in the precheck script:

  IndexError: list index out of range

This fix validates the output storage parameter before trying to use
it.  Since valid storage domain names cannot contain glob characters
or spaces, it avoids the problems above and improves the error message
that the user sees:

  $ virt-v2v [...] -o rhv-upload -os ''
  ...
  RuntimeError: The storage domain (-os) parameter ‘’ is not valid
  virt-v2v: error: failed server prechecks, see earlier errors

  $ virt-v2v [...] -o rhv-upload -os 'data*'
  ...
  RuntimeError: The storage domain (-os) parameter ‘data*’ is not valid
  virt-v2v: error: failed server prechecks, see earlier errors

Although the IndexError should no longer happen, I also added a
try...catch around that code to improve the error in case it still
happens.

Fixes: https://bugzilla.redhat.com/show_bug.cgi?id=1986386
Reported-by: Junqin Zhou
Thanks: Nir Soffer
---
 output/rhv-upload-precheck.py | 27 +--
 1 file changed, 21 insertions(+), 6 deletions(-)

diff --git a/output/rhv-upload-precheck.py b/output/rhv-upload-precheck.py
index 1dc1b8498a..ba125611ba 100644
--- a/output/rhv-upload-precheck.py
+++ b/output/rhv-upload-precheck.py
@@ -18,6 +18,7 @@
 
 import json
 import logging
+import re
 import sys
 
 from urllib.parse import urlparse
@@ -46,6 +47,15 @@ output_password = output_password.rstrip()
 parsed = urlparse(params['output_conn'])
 username = parsed.username or "admin@internal"
 
+# Check the storage domain name is valid
+# (https://bugzilla.redhat.com/show_bug.cgi?id=1986386#c1)
+# Also this means it cannot contain spaces or glob symbols, so
+# the search below is valid.
+output_storage = params['output_storage']
+if not re.match('^[-a-zA-Z0-9_]+$', output_storage):
+raise RuntimeError("The storage domain (-os) parameter ‘%s’ is not valid" %
+   output_storage)
+
 # Connect to the server.
 connection = sdk.Connection(
 url=params['output_conn'],
@@ -60,28 +70,33 @@ system_service = connection.system_service()
 
 # Check whether there is a datacenter for the specified storage.
 data_centers = system_service.data_centers_service().list(
-search='storage.name=%s' % params['output_storage'],
+search='storage.name=%s' % output_storage,
 case_sensitive=True,
 )
 if len(data_centers) == 0:
 storage_domains = system_service.storage_domains_service().list(
-search='name=%s' % params['output_storage'],
+search='name=%s' % output_storage,
 case_sensitive=True,
 )
 if len(storage_domains) == 0:
 # The storage domain does not even exist.
 raise RuntimeError("The storage domain ‘%s’ does not exist" %
-   (params['output_storage']))
+   output_storage)
 
 # The storage domain is not attached to a datacenter
 # (shouldn't happen, would fail on disk creation).
 raise RuntimeError("The storage domain ‘%s’ is not attached to a DC" %
-   (params['output_storage']))
+   output_storage)
 datacenter = data_centers[0]
 
 # Get the storage domain.
 storage_domains = connection.follow_link(datacenter.storage_domains)
-storage_domain = [sd for sd in storage_domains if sd.name == 
params['output_storage']][0]
+try:
+storage_domain = [sd for sd in storage_domains
+  if sd.name == output_storage][0]
+except IndexError:
+raise RuntimeError("The storage domain ‘%s’ does not exist" %
+   output_storage)
 
 # Get the cluster.
 clusters = connection.follow_link(datacenter.clusters)
@@ -90,7 +105,7 @@ if len(clusters) == 0:
 raise RuntimeError("The cluster ‘%s’ is not part of the DC ‘%s’, "
"where the storage domain ‘%s’ is" %
(params['rhv_cluster'], datacenter.name,
-params['output_storage']))
+output_storage))
 cluster = clusters[0]
 cpu = cluster.cpu
 if cpu.architecture == types.Architecture.UNDEFINED:
-- 
2.39.0

___
Libguestfs mailing list
Libguestfs@redhat.com
https://listman.redhat.com/mailman/listinfo/libguestfs


Re: [Libguestfs] [PATCH v2v] -o rhv-upload: Give a nicer error if the storage domain does not exist

2023-01-27 Thread Nir Soffer
On Fri, Jan 27, 2023 at 1:18 PM Nir Soffer  wrote:
>
> On Thu, Jan 26, 2023 at 2:31 PM Richard W.M. Jones  wrote:
> >
> > Fixes: https://bugzilla.redhat.com/show_bug.cgi?id=1986386
> > Reported-by: Junqin Zhou
> > ---
> >  output/rhv-upload-precheck.py | 7 ++-
> >  1 file changed, 6 insertions(+), 1 deletion(-)
> >
> > diff --git a/output/rhv-upload-precheck.py b/output/rhv-upload-precheck.py
> > index 1dc1b8498a..35ea021032 100644
> > --- a/output/rhv-upload-precheck.py
> > +++ b/output/rhv-upload-precheck.py
> > @@ -81,7 +81,12 @@ datacenter = data_centers[0]
> >
> >  # Get the storage domain.
> >  storage_domains = connection.follow_link(datacenter.storage_domains)
> > -storage_domain = [sd for sd in storage_domains if sd.name == 
> > params['output_storage']][0]
> > +try:
> > +storage_domain = [sd for sd in storage_domains \
> > +  if sd.name == params['output_storage']][0]
>
> Using `\` may work but it is needed. You can do this:
>
> storage_domain = [sd for sd in storage_domains
>if sd.name == params['output_storage']][0]
>
> This is also the common way to indent list comprehension that
> makes the expression more clear.
>
> > +except IndexError:
> > +raise RuntimeError("The storage domain ‘%s’ does not exist" %
> > +   params['output_storage'])
>
> The fix is safe and makes sense.
>
> Not sure why we list all storage domains when we already know the name,
> maybe Albert would like to clean up this mess later.

Like this:
https://github.com/oVirt/python-ovirt-engine-sdk4/blob/2aa50266056b7ee0b72597f346cbf0f006041566/examples/list_storage_domains.py#L93

___
Libguestfs mailing list
Libguestfs@redhat.com
https://listman.redhat.com/mailman/listinfo/libguestfs


Re: [Libguestfs] [PATCH v2v] -o rhv-upload: Give a nicer error if the storage domain does not exist

2023-01-27 Thread Nir Soffer
On Thu, Jan 26, 2023 at 2:31 PM Richard W.M. Jones  wrote:
>
> Fixes: https://bugzilla.redhat.com/show_bug.cgi?id=1986386
> Reported-by: Junqin Zhou
> ---
>  output/rhv-upload-precheck.py | 7 ++-
>  1 file changed, 6 insertions(+), 1 deletion(-)
>
> diff --git a/output/rhv-upload-precheck.py b/output/rhv-upload-precheck.py
> index 1dc1b8498a..35ea021032 100644
> --- a/output/rhv-upload-precheck.py
> +++ b/output/rhv-upload-precheck.py
> @@ -81,7 +81,12 @@ datacenter = data_centers[0]
>
>  # Get the storage domain.
>  storage_domains = connection.follow_link(datacenter.storage_domains)
> -storage_domain = [sd for sd in storage_domains if sd.name == 
> params['output_storage']][0]
> +try:
> +storage_domain = [sd for sd in storage_domains \
> +  if sd.name == params['output_storage']][0]

Using `\` may work but it is needed. You can do this:

storage_domain = [sd for sd in storage_domains
   if sd.name == params['output_storage']][0]

This is also the common way to indent list comprehension that
makes the expression more clear.

> +except IndexError:
> +raise RuntimeError("The storage domain ‘%s’ does not exist" %
> +   params['output_storage'])

The fix is safe and makes sense.

Not sure why we list all storage domains when we already know the name,
maybe Albert would like to clean up this mess later.

Nir

___
Libguestfs mailing list
Libguestfs@redhat.com
https://listman.redhat.com/mailman/listinfo/libguestfs


Re: [Libguestfs] [PATCH v2v] -o rhv-upload: Give a nicer error if the storage domain

2023-01-27 Thread Nir Soffer
On Thu, Jan 26, 2023 at 2:31 PM Richard W.M. Jones  wrote:
>
> https://bugzilla.redhat.com/show_bug.cgi?id=1986386
>
> My RHV instance is dead at the moment so I didn't do much more than
> check this compiles and passes the one test we have.  Also I want to
> spend as little time as possible on RHV outputs for virt-v2v since the
> RHV product will be discontinued soon.
>
> I did want to point out some things:
>
>  - The preceeding code is probably wrong.
>
> https://github.com/libguestfs/virt-v2v/blob/master/output/rhv-upload-transfer.py#L70
>
>It attempts to search for the output storage using:
>
> storage_domains = system_service.storage_domains_service().list(
> search='name=%s' % params['output_storage'],
> case_sensitive=True,
> )

I think the search is correct. This is explained in
https://bugzilla.redhat.com/1986386#c1

>I couldn't find any documentation about what can go into that
>search string, but it's clearly a lot more complicated than just
>pasting in the literal name after "name=".  At the very least,
>spaces are not permitted, see:
>
> https://github.com/libguestfs/virt-v2v/blob/master/output/rhv-upload-transfer.py#L70

True, search can be an expression.

>  - The bug reporter used "data*" as the name and I suspect that is
>parsed in some way (wildcard? regexp? I've no idea).

It is treated as glob pattern, also explained in comment 1.

>  - Probably for the same reason, the preceeding code ought to fail
>with an error if the output storage domain doesn't exist.  The fact
>we reach the code patched here at all also indicates some bug,
>maybe in the search string.
>
> As I say above, I don't especially care about any of this.

I'm not working on RHV since August 2022. Adding Albert who is current
RHV storage maintainer.

Nir

___
Libguestfs mailing list
Libguestfs@redhat.com
https://listman.redhat.com/mailman/listinfo/libguestfs