[Libguestfs] [PATCH v5 4/4] v2v: Add -o rhv-upload output mode.

Ondra Machacek omachace at redhat.com
Fri Mar 9 10:05:09 UTC 2018



On 03/08/2018 12:57 PM, Nir Soffer wrote:
> On Thu, Mar 8, 2018 at 11:37 AM Richard W.M. Jones <rjones at redhat.com 
> <mailto:rjones at redhat.com>> wrote:
> 
>     PROBLEMS:
>       - Target cluster defaults to "Default".
>       - Using Insecure = True, is that bad?
>       - -of qcow2 does not work, with multiple problems
>       - Need to attach disks to VMs somehow
> 
>     This adds a new output mode to virt-v2v.  virt-v2v -o rhv-upload
>     streams images directly to an oVirt or RHV >= 4 Data Domain using the
>     oVirt SDK v4.  It is more efficient than -o rhv because it does not
>     need to go via the Export Storage Domain, and is possible for humans
>     to use unlike -o vdsm.
> 
>     The implementation uses the Python SDK (‘ovirtsdk4’ module).  An
>     nbdkit Python 3 plugin translates NBD calls from qemu into HTTPS
>     requests to oVirt via the SDK.
>     ---
>       .gitignore                                |   3 +
>       v2v/Makefile.am                           |  26 ++-
>       v2v/cmdline.ml <http://cmdline.ml>                            | 
>     38 ++++
>       v2v/embed.sh                              |  45 ++++
>       v2v/output_rhv_upload.ml <http://output_rhv_upload.ml>           
>            | 330 ++++++++++++++++++++++++++++++
>       v2v/output_rhv_upload.mli                 |  27 +++
>       v2v/output_rhv_upload_createvm_source.mli |  19 ++
>       v2v/output_rhv_upload_plugin_source.mli   |  19 ++
>       v2v/output_rhv_upload_precheck_source.mli |  19 ++
>       v2v/rhv-upload-createvm.py                |  85 ++++++++
>       v2v/rhv-upload-plugin.py                  | 248 ++++++++++++++++++++++
>       v2v/rhv-upload-precheck.py                |  72 +++++++
>       v2v/virt-v2v.pod                          | 105 ++++++++--
>       13 files changed, 1021 insertions(+), 15 deletions(-)
> 
>     diff --git a/.gitignore b/.gitignore
>     index d72447d1d..211376eef 100644
>     --- a/.gitignore
>     +++ b/.gitignore
>     @@ -654,6 +654,9 @@ Makefile.in
>       /utils/qemu-speed-test/qemu-speed-test
>       /v2v/.depend
>       /v2v/oUnit-*
>     +/v2v/output_rhv_upload_createvm_source.ml
>     <http://output_rhv_upload_createvm_source.ml>
>     +/v2v/output_rhv_upload_plugin_source.ml
>     <http://output_rhv_upload_plugin_source.ml>
>     +/v2v/output_rhv_upload_precheck_source.ml
>     <http://output_rhv_upload_precheck_source.ml>
>       /v2v/real-*.d/
>       /v2v/real-*.img
>       /v2v/real-*.xml
>     diff --git a/v2v/Makefile.am b/v2v/Makefile.am
>     index c2eb31097..392eaefcc 100644
>     --- a/v2v/Makefile.am
>     +++ b/v2v/Makefile.am
>     @@ -22,12 +22,19 @@ generator_built = \
>              uefi.mli
> 
>       BUILT_SOURCES = \
>     -       $(generator_built)
>     +       $(generator_built) \
>     + output_rhv_upload_createvm_source.ml
>     <http://output_rhv_upload_createvm_source.ml> \
>     + output_rhv_upload_plugin_source.ml
>     <http://output_rhv_upload_plugin_source.ml> \
>     + output_rhv_upload_precheck_source.ml
>     <http://output_rhv_upload_precheck_source.ml>
> 
>       EXTRA_DIST = \
>              $(SOURCES_MLI) $(SOURCES_ML) $(SOURCES_C) \
>     copy_to_local.ml <http://copy_to_local.ml> \
>              copy_to_local.mli \
>     +       embed-code.sh \
>     +       rhv-upload-createvm.py \
>     +       rhv-upload-plugin.py \
>     +       rhv-upload-precheck.py \
>     v2v_slow_unit_tests.ml <http://v2v_slow_unit_tests.ml> \
>              v2v-slow-unit-tests.sh \
>     v2v_unit_tests.ml <http://v2v_unit_tests.ml> \
>     @@ -64,6 +71,10 @@ SOURCES_MLI = \
>              output_null.mli \
>              output_qemu.mli \
>              output_rhv.mli \
>     +       output_rhv_upload.mli \
>     +       output_rhv_upload_createvm_source.mli \
>     +       output_rhv_upload_plugin_source.mli \
>     +       output_rhv_upload_precheck_source.mli \
>              output_vdsm.mli \
>              parse_ovf_from_ova.mli \
>              parse_libvirt_xml.mli \
>     @@ -116,6 +127,10 @@ SOURCES_ML = \
>     output_local.ml <http://output_local.ml> \
>     output_qemu.ml <http://output_qemu.ml> \
>     output_rhv.ml <http://output_rhv.ml> \
>     + output_rhv_upload_createvm_source.ml
>     <http://output_rhv_upload_createvm_source.ml> \
>     + output_rhv_upload_plugin_source.ml
>     <http://output_rhv_upload_plugin_source.ml> \
>     + output_rhv_upload_precheck_source.ml
>     <http://output_rhv_upload_precheck_source.ml> \
>     + output_rhv_upload.ml <http://output_rhv_upload.ml> \
>     output_vdsm.ml <http://output_vdsm.ml> \
>     inspect_source.ml <http://inspect_source.ml> \
>     target_bus_assignment.ml <http://target_bus_assignment.ml> \
>     @@ -126,6 +141,15 @@ SOURCES_C = \
>              libvirt_utils-c.c \
>              qemuopts-c.c
> 
>     +# These files are generated and contain rhv-upload-*.py embedded as an
>     +# OCaml string.
>     +output_rhv_upload_createvm_source.ml
>     <http://output_rhv_upload_createvm_source.ml>: rhv-upload-createvm.py
>     +       ./embed.sh code $^ $@
>     +output_rhv_upload_plugin_source.ml
>     <http://output_rhv_upload_plugin_source.ml>: rhv-upload-plugin.py
>     +       ./embed.sh code $^ $@
>     +output_rhv_upload_precheck_source.ml
>     <http://output_rhv_upload_precheck_source.ml>: rhv-upload-precheck.py
>     +       ./embed.sh code $^ $@
>     +
>       if HAVE_OCAML
> 
>       bin_PROGRAMS = virt-v2v virt-v2v-copy-to-local
>     diff --git a/v2v/cmdline.ml <http://cmdline.ml> b/v2v/cmdline.ml
>     <http://cmdline.ml>
>     index d725ae022..c53d1703b 100644
>     --- a/v2v/cmdline.ml <http://cmdline.ml>
>     +++ b/v2v/cmdline.ml <http://cmdline.ml>
>     @@ -65,6 +65,8 @@ let parse_cmdline () =
>         let output_password = ref None in
>         let output_storage = ref None in
>         let password_file = ref None in
>     +  let rhv_cafile = ref None in
>     +  let rhv_direct = ref false in
>         let vddk_config = ref None in
>         let vddk_cookie = ref None in
>         let vddk_libdir = ref None in
>     @@ -143,6 +145,8 @@ let parse_cmdline () =
>           | "disk" | "local" -> output_mode := `Local
>           | "null" -> output_mode := `Null
>           | "ovirt" | "rhv" | "rhev" -> output_mode := `RHV
>     +    | "ovirt-upload" | "ovirt_upload" | "rhv-upload" | "rhv_upload" ->
>     +       output_mode := `RHV_Upload
>           | "qemu" -> output_mode := `QEmu
>           | "vdsm" -> output_mode := `VDSM
>           | s ->
>     @@ -229,6 +233,9 @@ let parse_cmdline () =
>           [ L"print-source" ], Getopt.Set print_source,
>                                           s_"Print source and stop";
>           [ L"qemu-boot" ], Getopt.Set qemu_boot, s_"Boot in qemu (-o
>     qemu only)";
>     +    [ L"rhv-cafile" ], Getopt.String ("ca.pem",
>     set_string_option_once "--rhv-cafile" rhv_cafile),
>     +                                    s_"For -o rhv-upload, set
>     ‘ca.pem’ file";
>     +    [ L"rhv-direct" ], Getopt.Set rhv_direct, s_"Use direct
>     transfer mode";
>           [ L"root" ],     Getopt.String ("ask|... ", set_root_choice),
>                                           s_"How to choose root filesystem";
>           [ L"vddk-config" ], Getopt.String ("filename",
>     set_string_option_once "--vddk-config" vddk_config),
>     @@ -322,6 +329,8 @@ read the man page virt-v2v(1).
>         let password_file = !password_file in
>         let print_source = !print_source in
>         let qemu_boot = !qemu_boot in
>     +  let rhv_cafile = !rhv_cafile in
>     +  let rhv_direct = !rhv_direct in
>         let root_choice = !root_choice in
>         let vddk_options =
>             { vddk_config = !vddk_config;
>     @@ -546,6 +555,35 @@ read the man page virt-v2v(1).
>             Output_rhv.output_rhv os output_alloc,
>             output_format, output_alloc
> 
>     +    | `RHV_Upload ->
>     +      let output_conn =
>     +        match output_conn with
>     +        | None ->
>     +           error (f_"-o rhv-upload: use ‘-oc’ to point to the oVirt
>     or RHV server REST API URL, which is usually
>     https://servername/ovirt-engine/api")
>     +        | Some oc -> oc in
>     +      (* In theory we could make the password optional in future. *)
>     +      let output_password =
>     +        match output_password with
>     +        | None ->
>     +           error (f_"-o rhv-upload: output password file was not
>     specified, use ‘-op’ to point to a file which contains the password
>     used to connect to the oVirt or RHV server")
>     +        | Some op -> op in
>     +      let os =
>     +        match output_storage with
>     +        | None ->
>     +           error (f_"-o rhv-upload: output storage was not
>     specified, use ‘-os’");
>     +        | Some os -> os in
>     +      if qemu_boot then
>     +        error_option_cannot_be_used_in_output_mode "rhv-upload"
>     "--qemu-boot";
>     +      let rhv_cafile =
>     +        match rhv_cafile with
>     +        | None ->
>     +           error (f_"-o rhv-upload: must use ‘--rhv-cafile’ to
>     supply the path to the oVirt or RHV server’s ‘ca.pem’ file")
>     +        | Some rhv_cafile -> rhv_cafile in
>     +      Output_rhv_upload.output_rhv_upload output_alloc output_conn
>     +                                          output_password os
>     +                                          rhv_cafile rhv_direct,
>     +      output_format, output_alloc
>     +
>           | `VDSM ->
>             if output_password <> None then
>               error_option_cannot_be_used_in_output_mode "vdsm" "-op";
>     diff --git a/v2v/embed.sh b/v2v/embed.sh
>     new file mode 100755
>     index 000000000..0a65cd428
>     --- /dev/null
>     +++ b/v2v/embed.sh
>     @@ -0,0 +1,45 @@
>     +#!/bin/bash -
>     +# Embed code or other content into an OCaml file.
>     +# 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.
>     +#
>     +# This program is distributed in the hope that it will be useful,
>     +# but WITHOUT ANY WARRANTY; without even the implied warranty of
>     +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
>     +# GNU General Public License for more details.
>     +#
>     +# You should have received a copy of the GNU General Public License
>     +# along with this program; if not, write to the Free
>     <https://maps.google.com/?q=write+to+the+Free&entry=gmail&source=g>
>     Software
>     +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
>     02110-1301 USA.
>     +
>     +# Embed code or other content into an OCaml file.
>     +#
>     +# It is embedded into a string.  As OCaml string literals have
>     virtually
>     +# no restrictions on length or content we only have to escape double
>     +# quotes for backslash characters.
>     +
>     +if [ $# -ne 3 ]; then
>     +    echo "embed.sh identifier input output"
>     +    exit 1
>     +fi
>     +
>     +ident="$1"
>     +input="$2"
>     +output="$3"
>     +
>     +rm -f "$output" "$output"-t
>     +
>     +exec >"$output"-t
>     +
>     +echo "(* Generated by embed.sh from $input *)"
>     +echo
>     +echo let "$ident" = '"'
>     +sed -e 's/\(["\]\)/\\\1/g' < "$input"
>     +echo '"'
>     +
>     +chmod -w "$output"-t
>     +mv "$output"-t "$output"
>     diff --git a/v2v/output_rhv_upload.ml <http://output_rhv_upload.ml>
>     b/v2v/output_rhv_upload.ml <http://output_rhv_upload.ml>
>     new file mode 100644
>     index 000000000..eecabd74d
>     --- /dev/null
>     +++ b/v2v/output_rhv_upload.ml <http://output_rhv_upload.ml>
>     @@ -0,0 +1,330 @@
>     +(* virt-v2v
>     + * Copyright (C) 2009-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.
>     + *
>     + * This program is distributed in the hope that it will be useful,
>     + * but WITHOUT ANY WARRANTY; without even the implied warranty of
>     + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
>     + * GNU General Public License for more details.
>     + *
>     + * You should have received a copy of the GNU General Public
>     License along
>     + * with this program; if not, write to the Free
>     <https://maps.google.com/?q=write+to+the+Free+&entry=gmail&source=g>Software
>     Foundation, Inc.,
>     + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
>     + *)
>     +
>     +open Printf
>     +open Unix
>     +
>     +open Std_utils
>     +open Tools_utils
>     +open Unix_utils
>     +open Common_gettext.Gettext
>     +
>     +open Types
>     +open Utils
>     +
>     +let python3 = "python3" (* Defined by PEP 394 *)
>     +let pidfile_timeout = 30
>     +let finalization_timeout = 5*60
>     +
>     +class output_rhv_upload output_alloc output_conn
>     +                        output_password output_storage
>     +                        rhv_cafile rhv_direct =
>     +  (* Create a temporary directory which will be deleted on exit. *)
>     +  let tmpdir =
>     +    let base_dir = (open_guestfs ())#get_cachedir () in
>     +    let t = Mkdtemp.temp_dir ~base_dir "rhvupload." in
>     +    rmdir_on_exit t;
>     +    t in
>     +
>     +  let diskid_file_of_id id = tmpdir // sprintf "diskid.%d" id in
>     +
>     +  (* Write the Python precheck, plugin and create VM to a temporary
>     file. *)
>     +  let precheck =
>     +    let precheck = tmpdir // "rhv-upload-precheck.py" in
>     +    with_open_out
>     +      precheck
>     +      (fun chan -> output_string chan
>     Output_rhv_upload_precheck_source.code);
>     +    precheck in
>     +  let plugin =
>     +    let plugin = tmpdir // "rhv-upload-plugin.py" in
>     +    with_open_out
>     +      plugin
>     +      (fun chan -> output_string chan
>     Output_rhv_upload_plugin_source.code);
>     +    plugin in
>     +  let createvm =
>     +    let createvm = tmpdir // "rhv-upload-createvm.py" in
>     +    with_open_out
>     +      createvm
>     +      (fun chan -> output_string chan
>     Output_rhv_upload_createvm_source.code);
>     +    createvm in
>     +
>     +  (* Is SELinux enabled and enforcing on the host? *)
>     +  let have_selinux =
>     +    0 = Sys.command "getenforce 2>/dev/null | grep -isq Enforcing" in
>     +
>     +  (* Check that nbdkit is available and new enough. *)
>     +  let error_unless_nbdkit_working () =
>     +    if 0 <> Sys.command "nbdkit --version >/dev/null" then
>     +      error (f_"nbdkit is not installed or not working.  It is
>     required to use ‘-o rhv-upload’.  See \"OUTPUT TO RHV\" in the
>     virt-v2v(1) manual.");
>     +
>     +    (* Check it's a new enough version.  The latest features we
>     +     * require are ‘--exit-with-parent’ and ‘--selinux-label’, both
>     +     * added in 1.1.14.  (We use 1.1.16 as the minimum here because
>     +     * it also adds the selinux=yes|no flag in --dump-config).
>     +     *)
>     +    let lines = external_command "nbdkit --help" in
>     +    let lines = String.concat " " lines in
>     +    if String.find lines "exit-with-parent" == -1 ||
>     +       String.find lines "selinux-label" == -1 then
>     +      error (f_"nbdkit is not new enough, you need to upgrade to
>     nbdkit ≥ 1.1.16")
>     +  in
>     +
>     +  (* Check that the python3 plugin is installed and working
>     +   * and can load the plugin script.
>     +   *)
>     +  let error_unless_nbdkit_python3_working () =
>     +    let cmd = sprintf "nbdkit %s %s --dump-plugin >/dev/null"
>     +                      python3 (quote plugin) in
>     +    if Sys.command cmd <> 0 then
>     +      error (f_"nbdkit Python 3 plugin is not installed or not
>     working.  It is required if you want to use ‘-o rhv-upload’.
>     +
>     +See also \"OUTPUT TO RHV\" in the virt-v2v(1) manual.")
>     +  in
>     +
>     +  (* Check that nbdkit was compiled with SELinux support (for the
>     +   * --selinux-label option).
>     +   *)
>     +  let error_unless_nbdkit_compiled_with_selinux () =
>     +    let lines = external_command "nbdkit --dump-config" in
>     +    (* In nbdkit <= 1.1.15 the selinux attribute was not present
>     +     * at all in --dump-config output so there was no way to tell.
>     +     * Ignore this case because there will be an error later when
>     +     * we try to use the --selinux-label parameter.
>     +     *)
>     +    if List.mem "selinux=no" (List.map String.trim lines) then
>     +      error (f_"nbdkit was compiled without SELinux support.  You
>     will have to recompile nbdkit with libselinux-devel installed, or
>     else set SELinux to Permissive mode while doing the conversion.")
>     +  in
>     +
>     +  (* JSON parameters which are invariant between disks. *)
>     +  let json_params = [
>     +    "output_conn", JSON.String output_conn;
>     +    "output_password", JSON.String output_password;
>     +    "output_storage", JSON.String output_storage;
>     +    "output_sparse", JSON.Bool (match output_alloc with
>     +                                | Sparse -> true
>     +                                | Preallocated -> false);
>     +    "rhv_cafile", JSON.String rhv_cafile;
>     +    "rhv_direct", JSON.Bool rhv_direct;
>     +  ] in
>     +
>     +  (* nbdkit command line args which are invariant between disks. *)
>     +  let nbdkit_args =
>     +    let args = [
>     +      "nbdkit";
>     +
>     +      "--foreground";           (* run in foreground *)
>     +      "--exit-with-parent";     (* exit when virt-v2v exits *)
>     +      "--newstyle";             (* use newstyle NBD protocol *)
>     +      "--exportname"; "/";
>     +
>     +      "python3";                (* use the nbdkit Python 3 plugin *)
>     +      plugin;                   (* Python plugin script *)
>     +    ] in
>     +    let args = if verbose () then args @ ["--verbose"] else args in
>     +    let args =
>     +      (* label the socket so qemu can open it *)
>     +      if have_selinux then
>     +        args @ ["--selinux-label"; "system_u:object_r:svirt_t:s0"]
>     +      else args in
>     +    args in
>     +
>     +object
>     +  inherit output
>     +
>     +  method precheck () =
>     +    error_unless_nbdkit_working ();
>     +    error_unless_nbdkit_python3_working ();
>     +    if have_selinux then
>     +      error_unless_nbdkit_compiled_with_selinux ()
>     +
>     +  method as_options =
>     +    "-o rhv-upload" ^
>     +    (match output_alloc with
>     +     | Sparse -> "" (* default, don't need to print it *)
>     +     | Preallocated -> " -oa preallocated") ^
>     +    sprintf " -oc %s -op %s -os %s"
>     +            output_conn output_password output_storage
>     +
>     +  method supported_firmware = [ TargetBIOS ]
>     +
>     +  method prepare_targets source targets =
>     +    let output_name = source.s_name in
>     +    let json_params =
>     +      ("output_name", JSON.String output_name) :: json_params in
>     +
>     +    (* Python code prechecks.  These can't run in #precheck because
>     +     * we need to know the name of the virtual machine.
>     +     *)
>     +    let json_param_file = tmpdir // "params.json" in
>     +    with_open_out
>     +      json_param_file
>     +      (fun chan -> output_string chan (JSON.string_of_doc
>     json_params));
>     +    if run_command [ python3; precheck; json_param_file ] <> 0 then
>     +      error (f_"failed server prechecks, see earlier errors");
>     +
>     +    (* Create an nbdkit instance for each disk and set the
>     +     * target URI to point to the NBD socket.
>     +     *)
>     +    List.map (
>     +      fun t ->
>     +        let id = t.target_overlay.ov_source.s_disk_id in
>     +        let disk_name = sprintf "%s-%03d" output_name id in
>     +        let json_params =
>     +          ("disk_name", JSON.String disk_name) :: json_params in
>     +
>     +        let disk_format =
>     +          match t.target_format with
>     +          | ("raw" | "qcow2") as fmt -> fmt
>     +          | _ ->
>     +             error (f_"rhv-upload: -of %s: Only output format ‘raw’
>     or ‘qcow2’ is supported.  If the input is in a different format then
>     force one of these output formats by adding either ‘-of raw’ or ‘-of
>     qcow2’ on the command line.")
>     +                   t.target_format in
>     +        let json_params =
>     +          ("disk_format", JSON.String disk_format) :: json_params in
>     +
>     +        let disk_size = t.target_overlay.ov_virtual_size in
>     +        let json_params =
>     +          ("disk_size", JSON.Int64 disk_size) :: json_params in
>     +
>     +        (* Ask the plugin to write the disk ID to a special file. *)
>     +        let diskid_file = diskid_file_of_id id in
>     +        let json_params =
>     +          ("diskid_file", JSON.String diskid_file) :: json_params in
>     +
>     +        (* Write the JSON parameters to a file. *)
>     +        let json_param_file = tmpdir // sprintf "params%d.json" id in
>     +        with_open_out
>     +          json_param_file
>     +          (fun chan -> output_string chan (JSON.string_of_doc
>     json_params));
>     +
>     +        let sock = tmpdir // sprintf "nbdkit%d.sock" id in
>     +        let pidfile = tmpdir // sprintf "nbdkit%d.pid" id in
>     +
>     +        (* Add common arguments to per-target arguments. *)
>     +        let args =
>     +          nbdkit_args @ [ "--pidfile"; pidfile;
>     +                          "--unix"; sock;
>     +                          sprintf "params=%s" json_param_file ] in
>     +
>     +        (* Print the full command we are about to run when
>     debugging. *)
>     +        if verbose () then (
>     +          eprintf "running nbdkit:\n";
>     +          List.iter (fun arg -> eprintf " %s" (quote arg)) args;
>     +          prerr_newline ()
>     +        );
>     +
>     +        (* Start an nbdkit instance in the background.  By using
>     +         * --exit-with-parent we don't have to worry about clean-up.
>     +         *)
>     +        let args = Array.of_list args in
>     +        let pid = fork () in
>     +        if pid = 0 then (
>     +          (* Child process (nbdkit). *)
>     +          execvp "nbdkit" args
>     +        );
>     +
>     +        (* Wait for the pidfile to appear so we know that nbdkit
>     +         * is listening for requests.
>     +         *)
>     +        if not (wait_for_file pidfile pidfile_timeout) then (
>     +          if verbose () then
>     +            error (f_"nbdkit did not start up.  See previous
>     debugging messages for problems.")
>     +          else
>     +            error (f_"nbdkit did not start up.  There may be errors
>     printed by nbdkit above.
>     +
>     +If the messages above are not sufficient to diagnose the problem
>     then add the ‘virt-v2v -v -x’ options and examine the debugging
>     output carefully.")
>     +        );
>     +
>     +        if have_selinux then (
>     +          (* Note that Unix domain sockets have both a file label and
>     +           * a socket/process label.  Using --selinux-label above
>     +           * only set the socket label, but we must also set the file
>     +           * label.
>     +           *)
>     +          ignore (
>     +              run_command ["chcon";
>     "system_u:object_r:svirt_image_t:s0";
>     +                           sock]
>     +          );
>     +        );
>     +        (* ... and the regular Unix permissions, in case qemu is
>     +         * running as another user.
>     +         *)
>     +        chmod sock 0o777;
>     +
>     +        (* Tell ‘qemu-img convert’ to write to the nbd socket which is
>     +         * connected to nbdkit.
>     +         *)
>     +        let json_params = [
>     +          "file.driver", JSON.String "nbd";
>     +          "file.path", JSON.String sock;
>     +          "file.export", JSON.String "/";
>     +        ] in
>     +        let target_file =
>     +          TargetURI ("json:" ^ JSON.string_of_doc json_params) in
>     +        { t with target_file }
>     +    ) targets
>     +
>     +  method create_metadata source targets _ guestcaps inspect
>     target_firmware =
>     +    (* Get the UUIDs of each disk image.  These files are written
>     +     * out by the nbdkit plugins on successful finalization of the
>     +     * transfer.
>     +     *)
>     +    let nr_disks = List.length targets in
>     +    let image_uuids =
>     +      List.map (
>     +        fun t ->
>     +          let id = t.target_overlay.ov_source.s_disk_id in
>     +          let diskid_file = diskid_file_of_id id in
>     +          if not (wait_for_file diskid_file finalization_timeout) then
>     +            error (f_"transfer of disk %d/%d failed, see earlier
>     error messages")
>     +                  (id+1) nr_disks;
>     +          let diskid = read_whole_file diskid_file in
>     +          diskid
>     +      ) targets in
>     +
>     +    (* We don't have the storage domain UUID, but instead we write
>     +     * in a magic value which the Python code (which can get it)
>     +     * will substitute.
>     +     *)
>     +    let sd_uuid = "@SD_UUID@" in
>     +
>     +    (* The volume and VM UUIDs are made up. *)
>     +    let vol_uuids = List.map (fun _ -> uuidgen ()) targets
>     +    and vm_uuid = uuidgen () in
>     +
>     +    (* Create the metadata. *)
>     +    let ovf =
>     +      Create_ovf.create_ovf source targets guestcaps inspect
>     +                            output_alloc
>     +                            sd_uuid image_uuids vol_uuids vm_uuid
>     +                            OVirt in
>     +    let ovf = DOM.doc_to_string ovf in
>     +
>     +    let json_param_file = tmpdir // "params.json" in
>     +    with_open_out
>     +      json_param_file
>     +      (fun chan -> output_string chan (JSON.string_of_doc
>     json_params));
>     +
>     +    let ovf_file = tmpdir // "vm.ovf" in
>     +    with_open_out ovf_file (fun chan -> output_string chan ovf);
>     +    if run_command [ python3; createvm; json_param_file; ovf_file ]
>     <> 0 then
>     +      error (f_"failed to create virtual machine, see earlier errors")
>     +
>     +end
>     +
>     +let output_rhv_upload = new output_rhv_upload
>     +let () = Modules_list.register_output_module "rhv-upload"
>     diff --git a/v2v/output_rhv_upload.mli b/v2v/output_rhv_upload.mli
>     new file mode 100644
>     index 000000000..3e7086f85
>     --- /dev/null
>     +++ b/v2v/output_rhv_upload.mli
>     @@ -0,0 +1,27 @@
>     +(* virt-v2v
>     + * Copyright (C) 2009-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.
>     + *
>     + * This program is distributed in the hope that it will be useful,
>     + * but WITHOUT ANY WARRANTY; without even the implied warranty of
>     + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
>     + * GNU General Public License for more details.
>     + *
>     + * You should have received a copy of the GNU General Public
>     License along
>     + * with this program; if not, write to the Free
>     <https://maps.google.com/?q=write+to+the+Free+&entry=gmail&source=g>Software
>     Foundation, Inc.,
>     + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
>     + *)
>     +
>     +(** [-o rhv-upload] target. *)
>     +
>     +val output_rhv_upload : Types.output_allocation -> string -> string ->
>     +                        string -> string -> bool ->
>     +                        Types.output
>     +(** [output_rhv_upload output_alloc output_conn output_password
>     output_storage
>     +     rhv_cafile rhv_direct]
>     +    creates and returns a new {!Types.output} object specialized
>     for writing
>     +    output to oVirt or RHV directly via RHV APIs. *)
>     diff --git a/v2v/output_rhv_upload_createvm_source.mli
>     b/v2v/output_rhv_upload_createvm_source.mli
>     new file mode 100644
>     index 000000000..c1bafa15b
>     --- /dev/null
>     +++ b/v2v/output_rhv_upload_createvm_source.mli
>     @@ -0,0 +1,19 @@
>     +(* virt-v2v
>     + * 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.
>     + *
>     + * This program is distributed in the hope that it will be useful,
>     + * but WITHOUT ANY WARRANTY; without even the implied warranty of
>     + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
>     + * GNU General Public License for more details.
>     + *
>     + * You should have received a copy of the GNU General Public
>     License along
>     + * with this program; if not, write to the Free
>     <https://maps.google.com/?q=write+to+the+Free+&entry=gmail&source=g>Software
>     Foundation, Inc.,
>     + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
>     + *)
>     +
>     +val code : string
>     diff --git a/v2v/output_rhv_upload_plugin_source.mli
>     b/v2v/output_rhv_upload_plugin_source.mli
>     new file mode 100644
>     index 000000000..c1bafa15b
>     --- /dev/null
>     +++ b/v2v/output_rhv_upload_plugin_source.mli
>     @@ -0,0 +1,19 @@
>     +(* virt-v2v
>     + * 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.
>     + *
>     + * This program is distributed in the hope that it will be useful,
>     + * but WITHOUT ANY WARRANTY; without even the implied warranty of
>     + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
>     + * GNU General Public License for more details.
>     + *
>     + * You should have received a copy of the GNU General Public
>     License along
>     + * with this program; if not, write to the Free
>     <https://maps.google.com/?q=write+to+the+Free+&entry=gmail&source=g>Software
>     Foundation, Inc.,
>     + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
>     + *)
>     +
>     +val code : string
>     diff --git a/v2v/output_rhv_upload_precheck_source.mli
>     b/v2v/output_rhv_upload_precheck_source.mli
>     new file mode 100644
>     index 000000000..c1bafa15b
>     --- /dev/null
>     +++ b/v2v/output_rhv_upload_precheck_source.mli
>     @@ -0,0 +1,19 @@
>     +(* virt-v2v
>     + * 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.
>     + *
>     + * This program is distributed in the hope that it will be useful,
>     + * but WITHOUT ANY WARRANTY; without even the implied warranty of
>     + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
>     + * GNU General Public License for more details.
>     + *
>     + * You should have received a copy of the GNU General Public
>     License along
>     + * with this program; if not, write to the Free
>     <https://maps.google.com/?q=write+to+the+Free+&entry=gmail&source=g>Software
>     Foundation, Inc.,
>     + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
>     + *)
>     +
>     +val code : string
>     diff --git a/v2v/rhv-upload-createvm.py b/v2v/rhv-upload-createvm.py
>     new file mode 100644
>     index 000000000..47c2574bb
>     --- /dev/null
>     +++ b/v2v/rhv-upload-createvm.py
>     @@ -0,0 +1,85 @@
>     +# -*- python -*-
>     +# oVirt or RHV upload create VM used by ‘virt-v2v -o rhv-upload’
>     +# 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.
>     +#
>     +# This program is distributed in the hope that it will be useful,
>     +# but WITHOUT ANY WARRANTY; without even the implied warranty of
>     +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
>     +# GNU General Public License for more details.
>     +#
>     +# You should have received a copy of the GNU General Public License
>     along
>     +# with this program; if not, write to the Free
>     <https://maps.google.com/?q=write+to+the+Free&entry=gmail&source=g>
>     Software Foundation, Inc.,
>     +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
>     +
>     +import json
>     +import logging
>     +import ovirtsdk4 as sdk
>     +import ovirtsdk4.types as types
>     +import sys
>     +import time
>     +
>     +from http.client import HTTPSConnection
>     +from urllib.parse import urlparse
>     +
>     +# Parameters are passed in via a JSON doc from the OCaml code.
>     +# Because this Python code ships embedded inside virt-v2v there
>     +# is no formal API here.
>     +params = None
>     +ovf = None                      # OVF file
>     +
>     +if len(sys.argv) != 3:
>     +    raise RuntimeError("incorrect number of parameters")
>     +
>     +# Parameters are passed in via a JSON document.
>     +with open(sys.argv[1], 'r') as fp:
>     +    params = json.load(fp)
>     +
>     +# What is passed in is a password file, read the actual password.
>     +with open(params['output_password'], 'r') as fp:
>     +    output_password = fp.read()
>     +output_password = output_password.rstrip()
>     +
>     +# Read the OVF document.
>     +with open(sys.argv[2], 'r') as fp:
>     +    ovf = fp.read()
>     +
>     +# Parse out the username from the output_conn URL.
>     +parsed = urlparse(params['output_conn'])
>     +username = parsed.username or "admin at internal"
>     +
>     +# Connect to the server.
>     +connection = sdk.Connection(
>     +    url = params['output_conn'],
>     +    username = username,
>     +    password = output_password,
>     +    ca_file = params['rhv_cafile'],
>     +    log = logging.getLogger(),
>     +    insecure = True,        # XXX?
> 
> 
> ovirt-imageio authentication is based on the assumption that the
> secret random url is passed from engine  to the user via https.
> if this access engine using clear text then yes it is bad :-)
> 
> Ondra, can you explain the semantics of incsecure=True?

I can see you are using 'ca_file' attribute. So you should use
insecure=True, only when user don't pass ca_file. If you pass
insecure=True we don't validate certificate, but https still can be
used. Preferred is of course using ca_file to validate certificates.

> 
>     +)
>     +
>     +system_service = connection.system_service()
>     +
>     +# Get the storage domain UUID and substitute it into the OVF doc.
>     +sds_service = system_service.storage_domains_service()
>     +sd = sds_service.list(search=("name=%s" % params['output_storage']))[0]
>     +sd_uuid = sd.id <http://sd.id>
>     +
>     +ovf.replace("@SD_UUID@", sd_uuid)
>     +
>     +vms_service = system_service.vms_service()
>     +vm = vms_service.add(
>     +    types.Vm(
>     +        cluster=types.Cluster(name = "Default"), # XXX
>     +        initialization=types.Initialization(
>     +            configuration = types.Configuration(
>     +                type = types.ConfigurationType.OVA,
>     +                data = ovf,
>     +            )
>     +        )
>     +    )
>     +)
>     diff --git a/v2v/rhv-upload-plugin.py b/v2v/rhv-upload-plugin.py
>     new file mode 100644
>     index 000000000..e65cb1c32
>     --- /dev/null
>     +++ b/v2v/rhv-upload-plugin.py
>     @@ -0,0 +1,248 @@
>     +# -*- python -*-
>     +# oVirt or RHV upload nbdkit plugin used by ‘virt-v2v -o rhv-upload’
>     +# 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.
>     +#
>     +# This program is distributed in the hope that it will be useful,
>     +# but WITHOUT ANY WARRANTY; without even the implied warranty of
>     +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
>     +# GNU General Public License for more details.
>     +#
>     +# You should have received a copy of the GNU General Public License
>     along
>     +# with this program; if not, write to the Free
>     <https://maps.google.com/?q=write+to+the+Free&entry=gmail&source=g>
>     Software Foundation, Inc.,
>     +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
>     +
>     +import builtins
>     +import json
>     +import logging
>     +import ovirtsdk4 as sdk
>     +import ovirtsdk4.types as types
>     +import ssl
>     +import sys
>     +import time
>     +
>     +from http.client import HTTPSConnection
>     +from urllib.parse import urlparse
>     +
>     +# Timeout to wait for oVirt disks to change status, or the transfer
>     +# object to finish initializing [seconds].
>     +timeout = 5*60
>     +
>     +# Parameters are passed in via a JSON doc from the OCaml code.
>     +# Because this Python code ships embedded inside virt-v2v there
>     +# is no formal API here.
>     +params = None
>     +
>     +def config(key, value):
>     +    global params
>     +
>     +    if key == "params":
>     +        with builtins.open(value, 'r') as fp:
>     +            params = json.load(fp)
>     +    else:
>     +        raise RuntimeError("unknown configuration key '%s'" % key)
>     +
>     +def config_complete():
>     +    global params
>     +
>     +    if params is None:
>     +        raise RuntimeError("missing configuration parameters")
>     +
>     +def open(readonly):
>     +    global params
> 
> 
> global is needed only when you try to bind something to the name, e.g.
> 
>       params = {}
> 
>     +
>     +    # Parse out the username from the output_conn URL.
>     +    parsed = urlparse(params['output_conn'])
>     +    username = parsed.username or "admin at internal"
>     +
>     +    # Read the password from file.
>     +    with builtins.open(params['output_password'], 'r') as fp:
>     +        password = fp.read()
>     +    password = password.rstrip()
>     +
>     +    # Connect to the server.
>     +    connection = sdk.Connection(
>     +        url = params['output_conn'],
>     +        username = username,
>     +        password = password,
>     +        ca_file = params['rhv_cafile'],
>     +        log = logging.getLogger(),
>     +        insecure = True,        # XXX?
>     +    )
>     +
>     +    system_service = connection.system_service()
>     +
>     +    # Create the disk.
>     +    disks_service = system_service.disks_service()
>     +    if params['disk_format'] == "raw":
>     +        disk_format = types.DiskFormat.RAW
>     +    else:
>     +        disk_format = types.DiskFormat.COW
>     +    disk = disks_service.add(
>     +        disk = types.Disk(
>     +            name = params['disk_name'],
>     +            description = "Uploaded by virt-v2v",
>     +            format = disk_format,
>     +            provisioned_size = params['disk_size'],
>     +            sparse = params['output_sparse'],
>     +            storage_domains = [
>     +                types.StorageDomain(
>     +                    name = params['output_storage'],
>     +                )
>     +            ],
>     +        )
>     +    )
>     +
>     +    # Wait till the disk is up, as the transfer can't start if the
>     +    # disk is locked:
>     +    disk_service = disks_service.disk_service(disk.id <http://disk.id>)
>     +
>     +    endt = time.time() + timeout
>     +    while True:
>     +        time.sleep(5)
>     +        disk = disk_service.get()
>     +        if disk.status == types.DiskStatus.OK:
>     +            break
>     +        if time.time() > endt:
>     +            raise RuntimeError("timed out waiting for disk to
>     become unlocked")
>     +
>     +    # Get a reference to the transfer service.
>     +    transfers_service = system_service.image_transfers_service()
>     +
>     +    # Create a new image transfer.
>     +    transfer = transfers_service.add(
>     +        types.ImageTransfer(
>     +            image = types.Image(
>     +                id = disk.id <http://disk.id>
>     +            )
>     +        )
>     +    )
>     +
>     +    # Get a reference to the created transfer service.
>     +    transfer_service =
>     transfers_service.image_transfer_service(transfer.id
>     <http://transfer.id>)
>     +
>     +    # After adding a new transfer for the disk, the transfer's status
>     +    # will be INITIALIZING.  Wait until the init phase is over. The
>     +    # actual transfer can start when its status is "Transferring".
>     +    endt = time.time() + timeout
>     +    while True:
>     +        time.sleep(5)
>     +        transfer = transfer_service.get()
>     +        if transfer.phase != types.ImageTransferPhase.INITIALIZING:
>     +            break
>     +        if time.time() > endt:
>     +            raise RuntimeError("timed out waiting for transfer
>     status != INITIALIZING")
>     +
>     +    # Now we have permission to start the transfer.
>     +    if params['rhv_direct']:
>     +        if transfer.transfer_url is None:
>     +            raise RuntimeError("direct upload to host not
>     supported, requires ovirt-engine >= 4.2 and only works when virt-v2v
>     is run within the oVirt/RHV environment, eg. on an ovirt node.")
>     +        destination_url = urlparse(transfer.transfer_url)
>     +    else:
>     +        destination_url = urlparse(transfer.proxy_url)
>     +
>     +    context = ssl.create_default_context()
>     +    context.load_verify_locations(cafile = params['rhv_cafile'])
>     +
>     +    http = HTTPSConnection(
>     +        destination_url.hostname,
>     +        destination_url.port,
>     +        context = context
>     +    )
>     +
>     +    # Save everything we need to make requests in the handle.
>     +    return {
>     +        'connection': connection,
>     +        'disk': disk,
>     +        'disk_service': disk_service,
>     +        'failed': False,
>     +        'http': http,
>     +        'path': destination_url.path,
>     +        'wrreqs': 0,
>     +        'transfer': transfer,
>     +        'transfer_service': transfer_service,
>     +    }
>     +
>     +def get_size(h):
>     +    global params
>     +
>     +    return params['disk_size']
>     +
>     +def pread(h, count, offset):
>     +    http = h['http']
>     +    transfer=h['transfer']
>     +    transfer_service=h['transfer_service']
>     +
>     +    http.putrequest("GET", h['path'])
>     +    http.putheader("Authorization", transfer.signed_ticket)
>     +    http.putheader("Range", str(offset) + "-" + str(count+offset-1))
> 
> 
> We use "bytes=start-end"
> 
> See 
> https://github.com/oVirt/ovirt-imageio/blob/master/daemon/test/server_test.py#L472
> 
> Also formatting cab be nicer and more consistent like this:
> 
>      "bytes=%d-%d" % (offset, offset + count - 1)
> 
>     +    http.endheaders()
>     +
>     +    r = http.getresponse()
>     +    if r.status != 200:
>     +        h['transfer_service'].pause()
>     +        h['failed'] = True
>     +        raise RuntimeError("could not read sector (%d, %d): %d: %s" %
>     +                           (offset, count, r.status, r.reason))
>     +    return r.read()
>     +
>     +def pwrite(h, buf, offset):
>     +    h['wrreqs'] += 1
>     +    count = len(buf)
>     +
>     +    http = h['http']
>     +    transfer=h['transfer']
>     +    transfer_service=h['transfer_service']
>     +
>     +    http.putrequest("PUT", h['path'])
>     +    http.putheader("Authorization", transfer.signed_ticket)
>     +    http.putheader("Range", str(offset) + "-" + str(count+offset-1))
> 
> 
> Range is used only for GET, for PUT we use Content-Range
> Current code will always write to 0-len(buf).
> 
> We use only the start value from the content range, so you can just send:
> 
>       http.putheader("Content-Range", "bytes %d-*/*" % offset)
> 
> See 
> https://github.com/oVirt/ovirt-imageio/blob/master/daemon/test/server_test.py#L393
> 
> I wonder if we should warn about unused Range header in PUT request.
> 
>     +    http.putheader("Content-Length", str(count))
>     +    http.endheaders()
>     +    http.send(buf)
>     +
>     +    r = http.getresponse()
>     +    if r.status != 200:
>     +        h['transfer_service'].pause()
>     +        h['failed'] = True
>     +        raise RuntimeError("could not write sector (%d, %d): %d: %s" %
>     +                           (offset, count, r.status, r.reason))
>     +
>     +# qemu-img convert starts by trying to zero/trim the whole device.
>     +# Since we've just created a new disk it's safe to ignore these
>     +# requests when they are the very first requests on the handle.
>     +# After that we must emulate them with writes.
> 
>     +def zero(h, count, offset, may_trim):
>     +    if h['wrreqs'] > 0:
>     +        buf = bytearray(count)
> 
> 
> Can count be a large number (e.g. 1G)?
> 
> If the value is not limited in the caller, this should create reasonable
> sized buffer and perform multiple writes
> 
>     +        pwrite(h, buf, count)
>     +
>     +def close(h):
>     +    global params
>     +
>     +    http = h['http']
>     +    connection = h['connection']
>     +
>     +    http.close()
>     +
>     +    # If we didn't fail, then finalize the transfer.
>     +    if not h['failed']:
>     +        disk = h['disk']
>     +        transfer_service=h['transfer_service']
>     +
>     +        transfer_service.finalize()
>     +
>     +        # Write the disk ID file.  Only do this on successful
>     completion.
>     +        with builtins.open(params['diskid_file'], 'w') as fp:
>     +            fp.write(disk.id <http://disk.id>)
>     +
>     +    # Otherwise if we did fail then we should delete the disk.
>     +    else:
>     +        disk_service = h['disk_service']
>     +        disk_service.remove()
>     +
>     +    connection.close()
>     diff --git a/v2v/rhv-upload-precheck.py b/v2v/rhv-upload-precheck.py
>     new file mode 100644
>     index 000000000..fc060be41
>     --- /dev/null
>     +++ b/v2v/rhv-upload-precheck.py
>     @@ -0,0 +1,72 @@
>     +# -*- python -*-
>     +# oVirt or RHV pre-upload checks used by ‘virt-v2v -o rhv-upload’
>     +# 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.
>     +#
>     +# This program is distributed in the hope that it will be useful,
>     +# but WITHOUT ANY WARRANTY; without even the implied warranty of
>     +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
>     +# GNU General Public License for more details.
>     +#
>     +# You should have received a copy of the GNU General Public License
>     along
>     +# with this program; if not, write to the Free
>     <https://maps.google.com/?q=write+to+the+Free&entry=gmail&source=g>
>     Software Foundation, Inc.,
>     +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
>     +
>     +import json
>     +import logging
>     +import ovirtsdk4 as sdk
>     +import ovirtsdk4.types as types
>     +import sys
>     +import time
>     +
>     +from http.client import HTTPSConnection
>     +from urllib.parse import urlparse
>     +
>     +# Parameters are passed in via a JSON doc from the OCaml code.
>     +# Because this Python code ships embedded inside virt-v2v there
>     +# is no formal API here.
>     +params = None
>     +
>     +if len(sys.argv) != 2:
>     +    raise RuntimeError("incorrect number of parameters")
>     +
>     +# Parameters are passed in via a JSON document.
>     +with open(sys.argv[1], 'r') as fp:
>     +    params = json.load(fp)
>     +
>     +# What is passed in is a password file, read the actual password.
>     +with open(params['output_password'], 'r') as fp:
>     +    output_password = fp.read()
>     +output_password = output_password.rstrip()
>     +
>     +# Parse out the username from the output_conn URL.
>     +parsed = urlparse(params['output_conn'])
>     +username = parsed.username or "admin at internal"
>     +
>     +# Connect to the server.
>     +connection = sdk.Connection(
>     +    url = params['output_conn'],
>     +    username = username,
>     +    password = output_password,
>     +    ca_file = params['rhv_cafile'],
>     +    log = logging.getLogger(),
>     +    insecure = True,        # XXX?
>     +)
>     +
>     +system_service = connection.system_service()
>     +
>     +# Find if a virtual machine already exists with that name.
>     +vms_service = system_service.vms_service()
>     +vms = vms_service.list(
>     +    search = ("name=%s" % params['output_name']),
>     +)
>     +if len(vms) > 0:
>     +    vm = vms[0]
>     +    raise RuntimeError("VM already exists with name ‘%s’, id ‘%s’" %
>     +                       (params['output_name'], vm.id <http://vm.id>))
>     +
>     +# Otherwise everything is OK, exit with no error.
>     diff --git a/v2v/virt-v2v.pod b/v2v/virt-v2v.pod
>     index d51e7ed2f..60cf682fd 100644
>     --- a/v2v/virt-v2v.pod
>     +++ b/v2v/virt-v2v.pod
>     @@ -6,15 +6,18 @@ virt-v2v - Convert a guest to use KVM
> 
>        virt-v2v -ic vpx://vcenter.example.com/Datacenter/esxi
>     <http://vcenter.example.com/Datacenter/esxi> vmware_guest
> 
>     - virt-v2v -ic vpx://vcenter.example.com/Datacenter/esxi
>     <http://vcenter.example.com/Datacenter/esxi> vmware_guest \
>     -   -o rhv -os rhv.nfs:/export_domain --network ovirtmgmt
>     -
>        virt-v2v -i libvirtxml guest-domain.xml -o local -os /var/tmp
> 
>        virt-v2v -i disk disk.img -o local -os /var/tmp
> 
>        virt-v2v -i disk disk.img -o glance
> 
>     + virt-v2v -ic vpx://vcenter.example.com/Datacenter/esxi
>     <http://vcenter.example.com/Datacenter/esxi> vmware_guest \
>     +   -o rhv-upload -oc
>     https://ovirt-engine.example.com/ovirt-engine/api \
>     +   -os ovirt-data -op /tmp/ovirt-admin-password \
>     +   --rhv-cafile /tmp/ca.pem --rhv-direct \
>     +   --network ovirtmgmt
>     +
>        virt-v2v -ic qemu:///system qemu_guest --in-place
> 
>       =head1 DESCRIPTION
>     @@ -42,9 +45,9 @@ libguestfs E<ge> 1.28.
>        Xen ───▶│ -i libvirt ──▶ │            │     │  (default) │
>        ... ───▶│  (default) │   │            │ ──┐ └────────────┘
>                └────────────┘   │            │ ─┐└──────▶ -o glance
>     - -i libvirtxml ─────────▶ │            │ ┐└─────────▶ -o rhv
>     - -i vmx ────────────────▶ │            │ └──────────▶ -o vdsm
>     -                          └────────────┘
>     + -i libvirtxml ─────────▶ │            │ ┐├─────────▶ -o rhv
>     + -i vmx ────────────────▶ │            │ │└─────────▶ -o vdsm
>     +                          └────────────┘ └──────────▶ -o rhv-upload
> 
>       Virt-v2v has a number of possible input and output modes, selected
>       using the I<-i> and I<-o> options.  Only one input and output mode can
>     @@ -103,20 +106,18 @@ For more information see L</INPUT FROM VMWARE
>     VCENTER SERVER> below.
>       =head2 Convert from VMware to RHV/oVirt
> 
>       This is the same as the previous example, except you want to send the
>     -guest to a RHV-M Export Storage Domain which is located remotely
>     -(over NFS) at C<rhv.nfs:/export_domain>.  If you are unclear about
>     -the location of the Export Storage Domain you should check the
>     -settings on your RHV-M management console.  Guest network
>     +guest to a RHV Data Domain using the RHV REST API.  Guest network
>       interface(s) are connected to the target network called C<ovirtmgmt>.
> 
>        virt-v2v -ic vpx://vcenter.example.com/Datacenter/esxi
>     <http://vcenter.example.com/Datacenter/esxi> vmware_guest \
>     -   -o rhv -os rhv.nfs:/export_domain --network ovirtmgmt
>     +   -o rhv-upload -oc
>     https://ovirt-engine.example.com/ovirt-engine/api \
>     +   -os ovirt-data -op /tmp/ovirt-admin-password \
>     +   --rhv-cafile /tmp/ca.pem --rhv-direct \
>     +   --network ovirtmgmt
> 
>       In this case the host running virt-v2v acts as a B<conversion server>.
> 
>     -Note that after conversion, the guest will appear in the RHV-M Export
>     -Storage Domain, from where you will need to import it using the RHV-M
>     -user interface.  (See L</OUTPUT TO RHV>).
>     +For more information see L</OUTPUT TO RHV> below.
> 
>       =head2 Convert from ESXi hypervisor over SSH to local libvirt
> 
>     @@ -509,6 +510,10 @@ written.
> 
>       This is the same as I<-o rhv>.
> 
>     +=item B<-o> B<ovirt-upload>
>     +
>     +This is the same as I<-o rhv-upload>.
>     +
>       =item B<-o> B<qemu>
> 
>       Set the output method to I<qemu>.
>     @@ -533,6 +538,16 @@ I<-os> parameter must also be used to specify
>     the location of the
>       Export Storage Domain.  Note this does not actually import the guest
>       into RHV.  You have to do that manually later using the UI.
> 
>     +See L</OUTPUT TO RHV (OLD METHOD)> below.
>     +
>     +=item B<-o> B<rhv-upload>
>     +
>     +Set the output method to I<rhv-upload>.
>     +
>     +The converted guest is written directly to a RHV Data Domain.
>     +This is a faster method than I<-o rhv>, but requires oVirt
>     +or RHV E<ge> 4.2.
>     +
>       See L</OUTPUT TO RHV> below.
> 
>       =item B<-o> B<vdsm>
>     @@ -627,6 +642,21 @@ virt-v2v finishes.
> 
>       This disables progress bars and other unnecessary output.
> 
>     +=item B<--rhv-cafile> F<ca.pem>
>     +
>     +For I<-o rhv-upload> (L<OUTPUT TO RHV>) only, the F<ca.pem> file
>     +(Certificate Authority), copied from F</etc/pki/ovirt-engine/ca.pem>
>     +on the oVirt engine.
>     +
>     +=item B<--rhv-direct>
>     +
>     +For I<-o rhv-upload> (L<OUTPUT TO RHV>) only, if this option is given
>     +then virt-v2v will attempt to directly upload the disk to the oVirt
>     +node, otherwise it will proxy the upload through the oVirt engine.
>     +Direct upload requires that you have network access to the oVirt
>     +nodes.  Non-direct upload is slightly slower but should work in all
>     +situations.
>     +
>       =item B<--root ask>
> 
>       =item B<--root single>
>     @@ -1870,6 +1900,53 @@ Define the final guest in libvirt:
> 
>       =head1 OUTPUT TO RHV
> 
>     +This new method to upload guests to oVirt or RHV directly via the REST
>     +API requires oVirt/RHV E<ge> 4.2.
>     +
>     +You need to specify I<-o rhv-upload> as well as the following extra
>     +parameters:
>     +
>     +=over 4
>     +
>     +=item I<-oc> C<https://ovirt-engine.example.com/ovirt-engine/api>
>     +
>     +The URL of the REST API which is usually the server name with
>     +C</ovirt-engine/api> appended, but might be different if you installed
>     +oVirt Engine on a different path.
>     +
>     +You can optionally add a username and port number to the URL.  If the
>     +username is not specified then virt-v2v defaults to using
>     +C<admin at internal> which is the typical superuser account for oVirt
>     +instances.
>     +
>     +=item I<-op> F<password-file>
>     +
>     +A file containing a password to be used when connecting to the oVirt
>     +engine.  Note the file should contain the whole password, B<without
>     +any trailing newline>, and for security the file should have mode
>     +C<0600> so that others cannot read it.
>     +
>     +=item I<-os> C<ovirt-data>
>     +
>     +The storage domain.
>     +
>     +=item I<--rhv-cafile> F<ca.pem>
>     +
>     +The F<ca.pem> file (Certificate Authority), copied from
>     +F</etc/pki/ovirt-engine/ca.pem> on the oVirt engine.
>     +
>     +=item I<--rhv-direct>
>     +
>     +If this option is given then virt-v2v will attempt to directly upload
>     +the disk to the oVirt node, otherwise it will proxy the upload through
>     +the oVirt engine.  Direct upload requires that you have network access
>     +to the oVirt nodes.  Non-direct upload is slightly slower but should
>     +work in all situations.
>     +
>     +=back
>     +
>     +=head1 OUTPUT TO RHV (OLD METHOD)
>     +
>       This section only applies to the I<-o rhv> output mode.  If you use
>       virt-v2v from the RHV-M user interface, then behind the scenes the
>       import is managed by VDSM using the I<-o vdsm> output mode (which end
>     --
>     2.13.2
> 
>     _______________________________________________
>     Libguestfs mailing list
>     Libguestfs at redhat.com <mailto:Libguestfs at redhat.com>
>     https://www.redhat.com/mailman/listinfo/libguestfs
> 




More information about the Libguestfs mailing list