UNIX socket support

Use cases

An example flow:

1. Select a running host from the data center

A user program selects a running host for image transfers using the API. The transfers can be distributed over all hosts in a data center to distribute the I/O.

2. Find the host uuid

Currently the host uuid used in engine side is not kept on the host, but we can find it using the host hardware uuid at /etc/vdsm/vdsm.id.

with 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_uuid=%s' % vdsm_id,
    case_sensitive=False,
)

3. Start an image transfer using the found host uuid

After finding the host uuid we can start an image transfer on that specific host.

transfers_service = system_service.image_transfers_service()
transfer = transfers_service.add(
    types.ImageTransfer(
        disk=types.Disk(id='123'),
        host=types.Host(id=hosts[0].id)
    )
)

4. Find the unix socket address using OPTIONS on the transfer_url

The first request to the server should be OPTIONS /images/ticket-uuid (to find the features supported by the server, e.g. “zero”). This will also expose the unix socket address.

OPTIONS /images/ticket-uuid
..
{
    "features": ["zero", "flush"],
    "unix_socket": "\0/org/ovirt/imageio"
}

5. Transfer the data using the unix socket

The user program needs to use a modified httplib.HTTPConnection to connect using socket address. Then it can use standard HTTPConnection APIs to perform the transfers.

Here is an example of UnixHTTPConnection class:

import socket
import six
from six.moves import http_client


class UnixHTTPConnection(http_client.HTTPConnection):
    """
    HTTP connection over unix domain socket.
    """

    def __init__(self, path, timeout=socket._GLOBAL_DEFAULT_TIMEOUT):
        self.path = path
        extra = {}
        if six.PY2:
            extra['strict'] = True
        http_client.HTTPConnection.__init__(
            self, "localhost", timeout=timeout, **extra)

    def connect(self):
        self.sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
        if self.timeout is not socket._GLOBAL_DEFAULT_TIMEOUT:
            self.sock.settimeout(self.timeout)
        self.sock.connect(self.path)

See upload example for more info: https://github.com/oVirt/ovirt-imageio/blob/master/examples/upload