#!/usr/bin/python3
# ovirt-imageio
# Copyright (C) 2018 Red Hat, Inc.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.

"""
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
    $ ./ovit-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)
