#!/usr/bin/python3 # SPDX-FileCopyrightText: Red Hat, Inc. # SPDX-License-Identifier: GPL-2.0-or-later """ imageio-client - upload and download images to ovirt-imageio server. Example usage ============= In this example we will upload Fedora 31 image to ovirt-imageio server, and dowload the uploaded image. Starting the imageio daemon --------------------------- In another shell, start the ovirt-imageio daemon running as current user and group: $ cd ../daemon $ ./ovirt-imageio -c test We can control the daemon now using ../daemon/test/daemon.sock. Creating a source image ----------------------- We can create a source image using virt-builder: $ virt-builder fedora-31 -o /var/tmp/upload.img Before we upload, we need to know the the image virtual size: $ qemu-img info /var/tmp/upload.img image: /var/tmp/upload.img file format: raw virtual size: 6 GiB (6442450944 bytes) disk size: 1.18 GiB Using the file backend ---------------------- The file backend is a simple backend uploading or downloading the data as is. We need to create the target image. ovirt-imageio does not manage storage, it only handle the data transfer. $ qemu-img create -f raw /var/tmp/disk.raw 6g Create a ticket, allowing access to the target image: $ cat file.json { "uuid": "file", "size": 6442450944, "url": "file:///var/tmp/disk.raw", "timeout": 3000, "ops": ["read", "write"] } Install the ticket: $ curl --unix-socket ../daemon/test/daemon.sock \ -X PUT \ --upload-file file.json \ http://localhost/tickets/file Upload the image: $ ./imageio-client --insecure upload \ /var/tmp/upload.img https://localhost:54322/images/file [ 100.00% ] 6.00 GiB, 2.06 seconds, 2.91 GiB/s Download the image: $ ./imageio-client --insecure download --format raw \ https://localhost:54322/images/file /dev/shm/download.img Formatting '/dev/shm/download.img', fmt=raw size=6442450944 [ 100.00% ] 6.00 GiB, 4.24 seconds, 1.42 GiB/s You can compare the original image to the downloaded image to check that the upload and download were correct: $ qemu-img compare /var/tmp/upload.img /dev/shm/download.img Delete the ticket: $ curl --unix-socket ../daemon/test/daemon.sock \ -X DELETE \ http://localhost/tickets/file Using the nbd backend --------------------- The nbd backend is advanced high preformance backend powered by qemu-nbd. It supports multiple connections, on-the-fly format conversion, and reporting image extents for sparse images. imageio-client always transfer raw data. During upload, imageio daemon write raw data to qemu-nbd, converting the data to the underlying image. During download, imageio daemon read raw data from qemu-nbd, converting the underlying image data from the image format. In this example, we will use a qcow2 target image. Create target image: $ qemu-img create -f qcow2 /var/tmp/disk.qcow2 6g Create a ticket, allowing access the target image: $ cat nbd.json { "uuid": "nbd", "size": 6442450944, "url": "nbd:unix:/tmp/nbd.sock", "timeout": 3000, "ops": ["read", "write"] } Install the ticket: $ curl --unix-socket ../daemon/test/daemon.sock \ -X PUT \ --upload-file nbd.json \ http://localhost/tickets/nbd In another shell, start qemu-nbd, serving the target image using the unix socket: $ qemu-nbd --socket=/tmp/nbd.sock \ --persistent \ --shared=8 \ --format=qcow2 \ --aio=native \ --cache=none \ --discard=unmap \ /var/tmp/disk.qcow2 Upload the image: $ ./imageio-client --insecure upload \ /var/tmp/upload.img https://localhost:54322/images/nbd [ 100.00% ] 6.00 GiB, 1.48 seconds, 4.04 GiB/s For reference, copying the image to qemu-nbd using qemu-img: $ time qemu-img convert -n -f raw -O raw -W /var/tmp/upload.img \ nbd:unix:/tmp/nbd.sock real 0m1.398s user 0m0.520s sys 0m0.546s Download the image: $ ./imageio-client --insecure download --format raw \ https://localhost:54322/images/nbd /dev/shm/download.img Formatting '/dev/shm/download.img', fmt=raw size=6442450944 [ 100.00% ] 6.00 GiB, 1.23 seconds, 4.89 GiB/s You can compare the original image to the downloaded image to check that the upload and download were correct: $ qemu-img compare /dev/shm/download.img /var/tmp/upload.img Images are identical. For reference, copying the image from qemu-nbd using qemu-img: $ time qemu-img convert -f raw -O raw -W nbd:unix:/tmp/nbd.sock \ /dev/shm/download.raw real 0m0.978s user 0m0.260s sys 0m0.653s Delete the ticket: $ curl --unix-socket ../daemon/test/daemon.sock \ -X DELETE \ http://localhost/tickets/nbd Finally shutdown qmeu-nbd in the other shell. """ import argparse import logging from ovirt_imageio import client def upload(args): with client.ProgressBar() as pb: client.upload( args.filename, args.url, args.cafile, buffer_size=args.buffer_size, max_workers=args.max_workers, secure=args.secure, progress=pb) def download(args): with client.ProgressBar() as pb: client.download( args.url, args.filename, args.cafile, fmt=args.format, incremental=args.incremental, buffer_size=args.buffer_size, max_workers=args.max_workers, secure=args.secure, progress=pb) parser = argparse.ArgumentParser(description="imageio client") parser.add_argument( "-c", "--cafile", help="path to CA certificate for verifying server certificate.") parser.add_argument( "--insecure", dest="secure", action="store_false", default=True, help=("do not verify server certificates and host name (not " "recommened).")) parser.add_argument( "-b", "--buffer-size", type=lambda v: int(v) * 1024, default=client.BUFFER_SIZE, help="buffer size in KiB for performance tuning (default {})" .format(client.BUFFER_SIZE // 1024)) parser.add_argument( "-w", "--max-workers", type=int, default=4, help="number of workers (default 4)") parser.add_argument( "-v", "--verbose", action="store_true", help="Be more verbose") commands = parser.add_subparsers(title="commands") upload_parser = commands.add_parser( "upload", help="upload image") upload_parser.set_defaults(command=upload) upload_parser.add_argument( "filename", help="path to image") upload_parser.add_argument( "url", help="transfer URL") download_parser = commands.add_parser( "download", help="download image") download_parser.set_defaults(command=download) download_parser.add_argument( "-f", "--format", choices=("raw", "qcow2"), default="qcow2", help=("download image format. The default qcow2 is usually best choice " "since it supports incremental backups")) download_parser.add_argument( "--incremental", action="store_true", help=("download only changed blocks. Can be used only during incremental " "backup and requires --format=qcow2")) download_parser.add_argument( "url", help="transfer URL") download_parser.add_argument( "filename", help="path to image") args = parser.parse_args() logging.basicConfig( level=logging.DEBUG if args.verbose else logging.WARNING, format=("%(asctime)s %(levelname)-7s (%(threadName)s) [%(name)s] " "%(message)s")) args.command(args)