Richard W.M. Jones
2018-Jun-21 12:35 UTC
[Libguestfs] v2v: -o rhv-upload: Use Unix domain socket to access imageio (RHBZ#1588088).
These two patches add support for using a Unix domain socket to directly access imageio in the case where imageio is running on the conversion host (usually that means virt-v2v is running on the RHV node and something else -- eg. CFME scripts -- arranges that the RHV node is the same one running imageio). Conversions in the normal case are not affected - they happen over TCP as usual. This was extremely hard to test, but I did eventually manage to test it both ways. The log from the Unix domain socket case is here: https://paste.fedoraproject.org/paste/RDhnpSaoUGI-jccIKy2e-g You can tell that the optimization was used because you will see this in the debug output: disk.id = 'c5bb72bf-b9ab-4797-9ecf-fcbf79679742' host.id = '87daa64d-b27e-4336-a156-973544be708b' transfer.id = '2207ddda-00fd-4caf-b02c-e025e8173da9' imageio features: flush=True trim=False zero=True unix_socket='\x00/org/ovirt/imageio' optimizing connection using unix socket '\x00/org/ovirt/imageio' (Note are the references to the unix socket.) This requires the absolute latest versions of imageio (1.4.0) and ovirt-engine (4.2.4-5) in order to get the optimization. However it should work (without optimization) with older versions. Thanks Nir Soffer and Daniel Erez in particular for help with this. Rich.
Richard W.M. Jones
2018-Jun-21 12:35 UTC
[Libguestfs] [PATCH 1/2] v2v: -o rhv-upload: Always fetch server options when opening the connection.
Previously we lazily requested the server options in the can_* callbacks. The can_* callbacks are always called by nbdkit straight after open, so this just adds complexity for no benefit. This change simply makes the code always fetch the server options during the open callback. This is — functionally at least — mostly just refactoring. However I also added a useful debug message so we can see what features the imageio server is offering. --- v2v/rhv-upload-plugin.py | 77 +++++++++++++++++++++++------------------------- 1 file changed, 37 insertions(+), 40 deletions(-) diff --git a/v2v/rhv-upload-plugin.py b/v2v/rhv-upload-plugin.py index 7c5084efd..419008517 100644 --- a/v2v/rhv-upload-plugin.py +++ b/v2v/rhv-upload-plugin.py @@ -165,63 +165,60 @@ def open(readonly): context = context ) + # The first request is to fetch the features of the server. + needs_auth = not params['rhv_direct'] + can_flush = False + can_trim = False + can_zero = False + + http.putrequest("OPTIONS", destination_url.path) + http.putheader("Authorization", transfer.signed_ticket) + http.endheaders() + + r = http.getresponse() + if r.status == 200: + # New imageio never needs authentication. + needs_auth = False + + j = json.loads(r.read()) + can_flush = "flush" in j['features'] + can_trim = "trim" in j['features'] + can_zero = "zero" in j['features'] + + # Old imageio servers returned either 405 Method Not Allowed or + # 204 No Content (with an empty body). If we see that we leave + # all the features as False and they will be emulated. + elif r.status == 405 or r.status == 204: + pass + + else: + raise RuntimeError("could not use OPTIONS request: %d: %s" % + (r.status, r.reason)) + + debug("imageio features: flush=%r trim=%r zero=%r unix_socket=%r" % + (can_flush, can_trim, can_zero, unix_socket)) + # Save everything we need to make requests in the handle. return { - 'can_flush': False, - 'can_trim': False, - 'can_zero': False, + 'can_flush': can_flush, + 'can_trim': can_trim, + 'can_zero': can_zero, 'connection': connection, 'disk': disk, 'disk_service': disk_service, 'failed': False, - 'got_options': False, 'highestwrite': 0, 'http': http, - 'needs_auth': not params['rhv_direct'], + 'needs_auth': needs_auth, 'path': destination_url.path, 'transfer': transfer, 'transfer_service': transfer_service, } -# Can we issue zero, trim or flush requests? -def get_options(h): - if h['got_options']: - return - h['got_options'] = True - - http = h['http'] - transfer = h['transfer'] - - http.putrequest("OPTIONS", h['path']) - http.putheader("Authorization", transfer.signed_ticket) - http.endheaders() - - r = http.getresponse() - if r.status == 200: - # New imageio never needs authentication. - h['needs_auth'] = False - - j = json.loads(r.read()) - h['can_zero'] = "zero" in j['features'] - h['can_trim'] = "trim" in j['features'] - h['can_flush'] = "flush" in j['features'] - - # Old imageio servers returned either 405 Method Not Allowed or - # 204 No Content (with an empty body). If we see that we leave - # all the features as False and they will be emulated. - elif r.status == 405 or r.status == 204: - pass - - else: - raise RuntimeError("could not use OPTIONS request: %d: %s" % - (r.status, r.reason)) - def can_trim(h): - get_options(h) return h['can_trim'] def can_flush(h): - get_options(h) return h['can_flush'] def get_size(h): -- 2.16.2
Richard W.M. Jones
2018-Jun-21 12:35 UTC
[Libguestfs] [PATCH 2/2] v2v: -o rhv-upload: Use Unix domain socket to access imageio (RHBZ#1588088).
In the case where virt-v2v runs on the same server as the imageio daemon that we are talking to, it may be possible to optimize access using a Unix domain socket. This is only an optimization. If it fails or if we're not running on the same server it will fall back to the usual HTTPS over TCP connection. --- v2v/rhv-upload-plugin.py | 61 +++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 60 insertions(+), 1 deletion(-) diff --git a/v2v/rhv-upload-plugin.py b/v2v/rhv-upload-plugin.py index 419008517..0c5eec7d3 100644 --- a/v2v/rhv-upload-plugin.py +++ b/v2v/rhv-upload-plugin.py @@ -19,11 +19,12 @@ import builtins import json import logging +import socket import ssl import sys import time -from http.client import HTTPSConnection +from http.client import HTTPSConnection, HTTPConnection from urllib.parse import urlparse import ovirtsdk4 as sdk @@ -117,6 +118,25 @@ def open(readonly): if time.time() > endt: raise RuntimeError("timed out waiting for disk to become unlocked") + # Get the current host. If it fails, don't worry. + host = None + try: + with builtin_open("/etc/vdsm/vdsm.id") as f: + vdsm_id = f.readline().strip() + + hosts_service = connection.system_service().hosts_service() + hosts = hosts_service.list( + search="hw_id=%s" % vdsm_id, + case_sensitive=False, + ) + if len(hosts) > 0: + host = hosts[0] + debug("host.id = %r" % host.id) + else: + debug("could not retrieve host with hw_id=%s" % vdsm_id) + except: + pass + # Get a reference to the transfer service. transfers_service = system_service.image_transfers_service() @@ -124,6 +144,7 @@ def open(readonly): transfer = transfers_service.add( types.ImageTransfer( disk = types.Disk(id = disk.id), + host = types.Host(id = host.id) if host else None, inactivity_timeout = 3600, ) ) @@ -170,6 +191,7 @@ def open(readonly): can_flush = False can_trim = False can_zero = False + unix_socket = None http.putrequest("OPTIONS", destination_url.path) http.putheader("Authorization", transfer.signed_ticket) @@ -184,6 +206,7 @@ def open(readonly): can_flush = "flush" in j['features'] can_trim = "trim" in j['features'] can_zero = "zero" in j['features'] + unix_socket = j.get('unix_socket') # Old imageio servers returned either 405 Method Not Allowed or # 204 No Content (with an empty body). If we see that we leave @@ -198,6 +221,15 @@ def open(readonly): debug("imageio features: flush=%r trim=%r zero=%r unix_socket=%r" % (can_flush, can_trim, can_zero, unix_socket)) + # If we are connected to the local host and the host features + # a unix_socket then we can reconnect to that. + if host and unix_socket: + try: + http = UnixHTTPConnection(unix_socket) + debug("optimizing connection using unix socket %r" % unix_socket) + except: + pass + # Save everything we need to make requests in the handle. return { 'can_flush': can_flush, @@ -451,3 +483,30 @@ def close(h): raise connection.close() + +# Modify http.client to work over a Unix domain socket. +# Derived from uhttplib written by Erik van Zijst under an MIT license. +# (https://pypi.org/project/uhttplib/) +# Ported to Python 3 by Irit Goihman. + +class UnsupportedError(Exception): + pass + +class _UnixMixin(object): + def set_tunnel(self, host, port=None, headers=None): + raise UnsupportedError("tunneling is not supported") + +class UnixHTTPConnection(_UnixMixin, HTTPConnection): + def __init__(self, path, timeout=socket._GLOBAL_DEFAULT_TIMEOUT): + self.path = path + HTTPConnection.__init__(self, "localhost", timeout=timeout) + + def connect(self): + self.sock = _create_unix_socket(self.timeout) + self.sock.connect(self.path) + +def _create_unix_socket(timeout): + sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) + if timeout is not socket._GLOBAL_DEFAULT_TIMEOUT: + sock.settimeout(timeout) + return sock -- 2.16.2
Richard W.M. Jones
2018-Jun-21 12:50 UTC
Re: [Libguestfs] [PATCH 1/2] v2v: -o rhv-upload: Always fetch server options when opening the connection.
On Thu, Jun 21, 2018 at 01:35:25PM +0100, Richard W.M. Jones wrote:> + j = json.loads(r.read()) > + can_flush = "flush" in j['features'] > + can_trim = "trim" in j['features'] > + can_zero = "zero" in j['features'] > + > + # Old imageio servers returned either 405 Method Not Allowed or > + # 204 No Content (with an empty body). If we see that we leave > + # all the features as False and they will be emulated. > + elif r.status == 405 or r.status == 204: > + pass > + > + else: > + raise RuntimeError("could not use OPTIONS request: %d: %s" % > + (r.status, r.reason)) > + > + debug("imageio features: flush=%r trim=%r zero=%r unix_socket=%r" % > + (can_flush, can_trim, can_zero, unix_socket))unix_socket in this debug message leaked in from the second patch. I'll fix this in the next version. Rich. -- Richard Jones, Virtualization Group, Red Hat http://people.redhat.com/~rjones Read my programming and virtualization blog: http://rwmj.wordpress.com virt-builder quickly builds VMs from scratch http://libguestfs.org/virt-builder.1.html
Tomáš Golembiovský
2018-Jun-21 21:26 UTC
Re: [Libguestfs] [PATCH 2/2] v2v: -o rhv-upload: Use Unix domain socket to access imageio (RHBZ#1588088).
On Thu, 21 Jun 2018 13:35:26 +0100 "Richard W.M. Jones" <rjones@redhat.com> wrote:> In the case where virt-v2v runs on the same server as the imageio > daemon that we are talking to, it may be possible to optimize access > using a Unix domain socket. > > This is only an optimization. If it fails or if we're not running on > the same server it will fall back to the usual HTTPS over TCP > connection. > --- > v2v/rhv-upload-plugin.py | 61 +++++++++++++++++++++++++++++++++++++++++++++++- > 1 file changed, 60 insertions(+), 1 deletion(-) > > diff --git a/v2v/rhv-upload-plugin.py b/v2v/rhv-upload-plugin.py > index 419008517..0c5eec7d3 100644 > --- a/v2v/rhv-upload-plugin.py > +++ b/v2v/rhv-upload-plugin.py > @@ -19,11 +19,12 @@ > import builtins > import json > import logging > +import socket > import ssl > import sys > import time > > -from http.client import HTTPSConnection > +from http.client import HTTPSConnection, HTTPConnection > from urllib.parse import urlparse > > import ovirtsdk4 as sdk > @@ -117,6 +118,25 @@ def open(readonly): > if time.time() > endt: > raise RuntimeError("timed out waiting for disk to become unlocked") > > + # Get the current host. If it fails, don't worry. > + host = None > + try: > + with builtin_open("/etc/vdsm/vdsm.id") as f: > + vdsm_id = f.readline().strip() > + > + hosts_service = connection.system_service().hosts_service() > + hosts = hosts_service.list( > + search="hw_id=%s" % vdsm_id, > + case_sensitive=False, > + ) > + if len(hosts) > 0: > + host = hosts[0] > + debug("host.id = %r" % host.id) > + else: > + debug("could not retrieve host with hw_id=%s" % vdsm_id) > + except: > + pass > + > # Get a reference to the transfer service. > transfers_service = system_service.image_transfers_service() > > @@ -124,6 +144,7 @@ def open(readonly): > transfer = transfers_service.add( > types.ImageTransfer( > disk = types.Disk(id = disk.id), > + host = types.Host(id = host.id) if host else None,proper check is 'host is not None'> inactivity_timeout = 3600, > ) > ) > @@ -170,6 +191,7 @@ def open(readonly): > can_flush = False > can_trim = False > can_zero = False > + unix_socket = None > > http.putrequest("OPTIONS", destination_url.path) > http.putheader("Authorization", transfer.signed_ticket) > @@ -184,6 +206,7 @@ def open(readonly): > can_flush = "flush" in j['features'] > can_trim = "trim" in j['features'] > can_zero = "zero" in j['features'] > + unix_socket = j.get('unix_socket') > > # Old imageio servers returned either 405 Method Not Allowed or > # 204 No Content (with an empty body). If we see that we leave > @@ -198,6 +221,15 @@ def open(readonly): > debug("imageio features: flush=%r trim=%r zero=%r unix_socket=%r" % > (can_flush, can_trim, can_zero, unix_socket)) > > + # If we are connected to the local host and the host features > + # a unix_socket then we can reconnect to that. > + if host and unix_socket:proper check is 'host is not None and unix_socket is not None'> + try: > + http = UnixHTTPConnection(unix_socket) > + debug("optimizing connection using unix socket %r" % unix_socket) > + except: > + pass > + > # Save everything we need to make requests in the handle. > return { > 'can_flush': can_flush, > @@ -451,3 +483,30 @@ def close(h): > raise > > connection.close() > + > +# Modify http.client to work over a Unix domain socket. > +# Derived from uhttplib written by Erik van Zijst under an MIT license. > +# (https://pypi.org/project/uhttplib/) > +# Ported to Python 3 by Irit Goihman. > + > +class UnsupportedError(Exception): > + pass > + > +class _UnixMixin(object): > + def set_tunnel(self, host, port=None, headers=None): > + raise UnsupportedError("tunneling is not supported") > + > +class UnixHTTPConnection(_UnixMixin, HTTPConnection): > + def __init__(self, path, timeout=socket._GLOBAL_DEFAULT_TIMEOUT): > + self.path = path > + HTTPConnection.__init__(self, "localhost", timeout=timeout) > + > + def connect(self): > + self.sock = _create_unix_socket(self.timeout) > + self.sock.connect(self.path) > + > +def _create_unix_socket(timeout): > + sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) > + if timeout is not socket._GLOBAL_DEFAULT_TIMEOUT: > + sock.settimeout(timeout) > + return sock > -- > 2.16.2 >-- Tomáš Golembiovský <tgolembi@redhat.com>
Nir Soffer
2018-Jun-25 23:46 UTC
Re: [Libguestfs] v2v: -o rhv-upload: Use Unix domain socket to access imageio (RHBZ#1588088).
On Thu, Jun 21, 2018 at 3:35 PM Richard W.M. Jones <rjones@redhat.com> wrote:> These two patches add support for using a Unix domain socket to > directly access imageio in the case where imageio is running on the > conversion host (usually that means virt-v2v is running on the RHV > node and something else -- eg. CFME scripts -- arranges that the RHV > node is the same one running imageio). >Actually CFME does not know anything about this optimization. It is virt-v2v starting the transfer on the same host it is running on.> > Conversions in the normal case are not affected - they happen over TCP > as usual.> This was extremely hard to test,Why? is this something that we can improve in ovirt?> but I did eventually manage to test > it both ways.Do you mean both using https and unix socket?> The log from the Unix domain socket case is here: > > https://paste.fedoraproject.org/paste/RDhnpSaoUGI-jccIKy2e-g> > You can tell that the optimization was used because you will see this > in the debug output: > > disk.id = 'c5bb72bf-b9ab-4797-9ecf-fcbf79679742' > host.id = '87daa64d-b27e-4336-a156-973544be708b' > transfer.id = '2207ddda-00fd-4caf-b02c-e025e8173da9' > imageio features: flush=True trim=False zero=True > unix_socket='\x00/org/ovirt/imageio' > optimizing connection using unix socket '\x00/org/ovirt/imageio' > > (Note are the references to the unix socket.) > > This requires the absolute latest versions of imageio (1.4.0) and > ovirt-engine (4.2.4-5) in order to get the optimization. However it > should work (without optimization) with older versions. > > Thanks Nir Soffer and Daniel Erez in particular for help with this. > > Rich. > >
Richard W.M. Jones
2018-Jun-26 08:13 UTC
Re: [Libguestfs] v2v: -o rhv-upload: Use Unix domain socket to access imageio (RHBZ#1588088).
On Tue, Jun 26, 2018 at 02:46:39AM +0300, Nir Soffer wrote:> On Thu, Jun 21, 2018 at 3:35 PM Richard W.M. Jones <rjones@redhat.com> > wrote: > > > These two patches add support for using a Unix domain socket to > > directly access imageio in the case where imageio is running on the > > conversion host (usually that means virt-v2v is running on the RHV > > node and something else -- eg. CFME scripts -- arranges that the RHV > > node is the same one running imageio). > > > > Actually CFME does not know anything about this optimization. It is virt-v2v > starting the transfer on the same host it is running on. > > > > > > Conversions in the normal case are not affected - they happen over TCP > > as usual. > > > > This was extremely hard to test, > > > Why? is this something that we can improve in ovirt?I don't think there's anything needed in oVirt. It's just that to test it I had to build virt-v2v on the RHV node (running RHEL 7) which was quite complicated.> > but I did eventually manage to test > > it both ways. > > > Do you mean both using https and unix socket?Yes. Rich. -- Richard Jones, Virtualization Group, Red Hat http://people.redhat.com/~rjones Read my programming and virtualization blog: http://rwmj.wordpress.com virt-df lists disk usage of guests without needing to install any software inside the virtual machine. Supports Linux and Windows. http://people.redhat.com/~rjones/virt-df/
Possibly Parallel Threads
- v2v: -o rhv-upload: Use Unix domain socket to access imageio (RHBZ#1588088).
- [PATCH v3] v2v: -o rhv-upload: Use Unix domain socket to access imageio (RHBZ#1588088).
- Re: [PATCH v3] v2v: -o rhv-upload: Use Unix domain socket to access imageio (RHBZ#1588088).
- [PATCH 00/18] rvh-upload: Various fixes and cleanups
- [PATCH v2 00/11] rvh-upload: Various fixes and cleanups