Python SDK version 4.1.4 introduced support for sending asynchronous requests and HTTP pipelining.

This blog post explains those terms and will show you an example how to use the Python SDK in an asynchronous manner.

Asynchronous Requests

When using asynchronous requests, the client sends the request and defines a method (usually called callback), which should be called after the response is received but the client is not waiting for the response. In order for SDK to work in an asynchronous fashion, we introduced two new features to our SDK: multiple connections and HTTP pipelining.

These features provide significant value when the user wishes to fetch the inventory of the oVirt system. The time to fetch the inventory may be significantly decreased, too. A comparison of the synchronous and asynchronous requests folows.

Multiple Connections

Previously the SDK used only a single open connection that sequentially sent the requests according to user program and always waited for the server response for corresponding request.

In the new version of the SDK, the user can specify the number of connections the SDK should create to the server, and the specific requests created by user program uses those connections in parallel.

HTTP Pipelining

As you can see at the image below, the HTTP requests are executed sequentially by default. The next request in order can be executed, when the previous request is received.

With HTTP pipelining, a client can send multiple requests without waiting for ther server response. Only idempotent HTTP methods can be pipelined.

With pipelining disabled            With pipelining enabled

       |     |                            |      |
       |    |                            |     |
       |    |                            |     |
       |    |                            |    |
       |    |                            |    |
       |    /|                            |    |
       |   / |                            |    /| 
       |  /  |                            |    /|
CLIENT | /   | SERVER              CLIENT |   / /| SERVER
       |    |                            |  / / |
       |    |                            | / /  |
       |    |                            |/ /   |
       |    |                            | /    |
       |    /|                            |      |
       |   / |                            
       |  /  |                            
       | /   |                            

SDK Implementation

In order not to break the backward compatibility of the SDK, we have introduced a new Boolean parameter called wait, to the methods of SDK services.

The default value is True, so it’s working in synchronous fashion. If the user sends the wait=False, the SDK will send a request specified by user program and will return the Future object,
which is defined as follows:

class Future(object):
    """
    Instances of this class are returned for operations that specify the
    `wait=False` parameter.
    """

    def wait(self):
        """
        Waits till the result of the operation that created this future is
        available.
        """

The user will need to call the wait method in order to wait for the response. The wait method returns the resulting object.

Example

This example will fetch all the VMs in oVirt and also fetch relevant VM data like network interfaces, disks and permissions. The additional data of the VM are fetched asynchronously.

import ovirtsdk4 as sdk

connection = sdk.Connection(
    url='https://example.engine.com/ovirt-engine/api',
    username='admin@internal',
    password='123456',
    ca_files='ca.pem',
    connections=5,
    pipeline=5,
)

system_service = connection.system_service()

vms_service = system_service.vms_service()
vms = system_service.vms_service().list()

futures = {}
for vm in vms:
    vm_service = vms_service.vm_service(vm.id)
    disks_future = vm_service.disk_attachments_service().list(wait=False)
    nics_future = vm_service.nics_service().list(wait=False)
    perms_future = vm_service.permissions_service().list(wait=False)
    futures[vm.name] = [disks_future, nics_future, perms_future]

for vm_name, vm_resources in futures.iteritems():
    for resource in vm_resources:
        resource.wait()

connection.close()

You can check another example in the git repository of the oVirt Python SDK, under the examples sub directory.

Benchmark

I performed a small benchmark test on my small oVirt setup with about 100 virtual machines and
executed the example above, with the following result:

omachace ~ $ time python sync.py 

real   0m4.746s
user   0m4.109s
sys    0m0.447s

omachace ~ $ time python async.py 

real   0m2.569s
user   0m2.015s
sys    0m0.179s