|
|
97ae69 |
From d7a1c9c0bb074d0e5c1878a38a6b6315d33be89c Mon Sep 17 00:00:00 2001
|
|
|
97ae69 |
From: "Richard W.M. Jones" <rjones@redhat.com>
|
|
|
97ae69 |
Date: Mon, 12 Feb 2018 16:45:02 +0000
|
|
|
97ae69 |
Subject: [PATCH] v2v: Add -o rhv-upload output mode (RHBZ#1557273).
|
|
|
97ae69 |
MIME-Version: 1.0
|
|
|
97ae69 |
Content-Type: text/plain; charset=UTF-8
|
|
|
97ae69 |
Content-Transfer-Encoding: 8bit
|
|
|
97ae69 |
|
|
|
97ae69 |
This adds a new output mode to virt-v2v. virt-v2v -o rhv-upload
|
|
|
97ae69 |
streams images directly to an oVirt or RHV >= 4 Data Domain using the
|
|
|
97ae69 |
oVirt SDK v4. It is more efficient than -o rhv because it does not
|
|
|
97ae69 |
need to go via the Export Storage Domain, and is possible for humans
|
|
|
97ae69 |
to use unlike -o vdsm.
|
|
|
97ae69 |
|
|
|
97ae69 |
The implementation uses the Python SDK (‘ovirtsdk4’ module). An
|
|
|
97ae69 |
nbdkit Python 3 plugin translates NBD calls from qemu into HTTPS
|
|
|
97ae69 |
requests to oVirt via the SDK.
|
|
|
97ae69 |
|
|
|
97ae69 |
(cherry picked from commit cc04573927cca97de60d544d37467e67c25867a7)
|
|
|
97ae69 |
---
|
|
|
97ae69 |
.gitignore | 3 +
|
|
|
97ae69 |
TODO | 27 ++
|
|
|
97ae69 |
v2v/Makefile.am | 30 +-
|
|
|
97ae69 |
v2v/cmdline.ml | 41 ++
|
|
|
97ae69 |
v2v/embed.sh | 48 +++
|
|
|
97ae69 |
v2v/output_rhv_upload.ml | 401 +++++++++++++++++++
|
|
|
97ae69 |
v2v/output_rhv_upload.mli | 33 ++
|
|
|
97ae69 |
v2v/output_rhv_upload_createvm_source.mli | 19 +
|
|
|
97ae69 |
v2v/output_rhv_upload_plugin_source.mli | 19 +
|
|
|
97ae69 |
v2v/output_rhv_upload_precheck_source.mli | 19 +
|
|
|
97ae69 |
v2v/rhv-upload-createvm.py | 86 +++++
|
|
|
97ae69 |
v2v/rhv-upload-plugin.py | 445 ++++++++++++++++++++++
|
|
|
97ae69 |
v2v/rhv-upload-precheck.py | 73 ++++
|
|
|
97ae69 |
v2v/test-v2v-o-rhv-upload-oo-query.sh | 38 ++
|
|
|
97ae69 |
v2v/test-v2v-python-syntax.sh | 45 +++
|
|
|
97ae69 |
v2v/virt-v2v.pod | 138 ++++++-
|
|
|
97ae69 |
16 files changed, 1447 insertions(+), 18 deletions(-)
|
|
|
97ae69 |
create mode 100755 v2v/embed.sh
|
|
|
97ae69 |
create mode 100644 v2v/output_rhv_upload.ml
|
|
|
97ae69 |
create mode 100644 v2v/output_rhv_upload.mli
|
|
|
97ae69 |
create mode 100644 v2v/output_rhv_upload_createvm_source.mli
|
|
|
97ae69 |
create mode 100644 v2v/output_rhv_upload_plugin_source.mli
|
|
|
97ae69 |
create mode 100644 v2v/output_rhv_upload_precheck_source.mli
|
|
|
97ae69 |
create mode 100644 v2v/rhv-upload-createvm.py
|
|
|
97ae69 |
create mode 100644 v2v/rhv-upload-plugin.py
|
|
|
97ae69 |
create mode 100644 v2v/rhv-upload-precheck.py
|
|
|
97ae69 |
create mode 100755 v2v/test-v2v-o-rhv-upload-oo-query.sh
|
|
|
97ae69 |
create mode 100755 v2v/test-v2v-python-syntax.sh
|
|
|
97ae69 |
|
|
|
97ae69 |
diff --git a/.gitignore b/.gitignore
|
|
|
97ae69 |
index e67013478..ecdac6df3 100644
|
|
|
97ae69 |
--- a/.gitignore
|
|
|
97ae69 |
+++ b/.gitignore
|
|
|
97ae69 |
@@ -671,6 +671,9 @@ Makefile.in
|
|
|
97ae69 |
/utils/qemu-speed-test/qemu-speed-test
|
|
|
97ae69 |
/v2v/.depend
|
|
|
97ae69 |
/v2v/oUnit-*
|
|
|
97ae69 |
+/v2v/output_rhv_upload_createvm_source.ml
|
|
|
97ae69 |
+/v2v/output_rhv_upload_plugin_source.ml
|
|
|
97ae69 |
+/v2v/output_rhv_upload_precheck_source.ml
|
|
|
97ae69 |
/v2v/real-*.d/
|
|
|
97ae69 |
/v2v/real-*.img
|
|
|
97ae69 |
/v2v/real-*.xml
|
|
|
97ae69 |
diff --git a/TODO b/TODO
|
|
|
97ae69 |
index 2e37ce67c..d196a3f6b 100644
|
|
|
97ae69 |
--- a/TODO
|
|
|
97ae69 |
+++ b/TODO
|
|
|
97ae69 |
@@ -570,3 +570,30 @@ Subsecond handling in virt-diff, virt-ls
|
|
|
97ae69 |
|
|
|
97ae69 |
Handle nanoseconds properly. You should be able to specify them on
|
|
|
97ae69 |
the command line and display them.
|
|
|
97ae69 |
+
|
|
|
97ae69 |
+virt-v2v -o rhv-upload
|
|
|
97ae69 |
+----------------------
|
|
|
97ae69 |
+
|
|
|
97ae69 |
+* Set or disable the ticket timeout. The default is going to be
|
|
|
97ae69 |
+ increased (from current 60 seconds), so maybe we won't have to
|
|
|
97ae69 |
+ set it. See also:
|
|
|
97ae69 |
+ https://bugzilla.redhat.com/show_bug.cgi?id=1563278
|
|
|
97ae69 |
+
|
|
|
97ae69 |
+* qcow2 cannot be supported yet because there is not yet any
|
|
|
97ae69 |
+ concept in imageio of read+write handles.
|
|
|
97ae69 |
+ https://bugzilla.redhat.com/show_bug.cgi?id=1563299
|
|
|
97ae69 |
+
|
|
|
97ae69 |
+* preallocated cannot be supported yet because imageio doesn't
|
|
|
97ae69 |
+ know how to zero the image efficiently, instead it runs an
|
|
|
97ae69 |
+ fallocate process which writes to every block and that takes
|
|
|
97ae69 |
+ many minutes.
|
|
|
97ae69 |
+
|
|
|
97ae69 |
+* Really check what insecure/rhv_cafile do and implement it correctly.
|
|
|
97ae69 |
+
|
|
|
97ae69 |
+* Measure and resolve performance problems.
|
|
|
97ae69 |
+
|
|
|
97ae69 |
+* Allocated image size is unknown for v2v uploads, but imageio needs
|
|
|
97ae69 |
+ to know it. We pass initial_size == provisioned_size == virtual size.
|
|
|
97ae69 |
+ That can't be fixed from the v2v side.
|
|
|
97ae69 |
+
|
|
|
97ae69 |
+* There are unresolved issues about how to clean up disks on failure.
|
|
|
97ae69 |
diff --git a/v2v/Makefile.am b/v2v/Makefile.am
|
|
|
97ae69 |
index 5fcfeadba..affc00a8b 100644
|
|
|
97ae69 |
--- a/v2v/Makefile.am
|
|
|
97ae69 |
+++ b/v2v/Makefile.am
|
|
|
97ae69 |
@@ -22,12 +22,19 @@ generator_built = \
|
|
|
97ae69 |
uefi.mli
|
|
|
97ae69 |
|
|
|
97ae69 |
BUILT_SOURCES = \
|
|
|
97ae69 |
- $(generator_built)
|
|
|
97ae69 |
+ $(generator_built) \
|
|
|
97ae69 |
+ output_rhv_upload_createvm_source.ml \
|
|
|
97ae69 |
+ output_rhv_upload_plugin_source.ml \
|
|
|
97ae69 |
+ output_rhv_upload_precheck_source.ml
|
|
|
97ae69 |
|
|
|
97ae69 |
EXTRA_DIST = \
|
|
|
97ae69 |
$(SOURCES_MLI) $(SOURCES_ML) $(SOURCES_C) \
|
|
|
97ae69 |
copy_to_local.ml \
|
|
|
97ae69 |
copy_to_local.mli \
|
|
|
97ae69 |
+ embed-code.sh \
|
|
|
97ae69 |
+ rhv-upload-createvm.py \
|
|
|
97ae69 |
+ rhv-upload-plugin.py \
|
|
|
97ae69 |
+ rhv-upload-precheck.py \
|
|
|
97ae69 |
v2v_unit_tests.ml \
|
|
|
97ae69 |
virt-v2v.pod \
|
|
|
97ae69 |
virt-v2v-copy-to-local.pod
|
|
|
97ae69 |
@@ -62,6 +69,10 @@ SOURCES_MLI = \
|
|
|
97ae69 |
output_null.mli \
|
|
|
97ae69 |
output_qemu.mli \
|
|
|
97ae69 |
output_rhv.mli \
|
|
|
97ae69 |
+ output_rhv_upload.mli \
|
|
|
97ae69 |
+ output_rhv_upload_createvm_source.mli \
|
|
|
97ae69 |
+ output_rhv_upload_plugin_source.mli \
|
|
|
97ae69 |
+ output_rhv_upload_precheck_source.mli \
|
|
|
97ae69 |
output_vdsm.mli \
|
|
|
97ae69 |
parse_ova.mli \
|
|
|
97ae69 |
parse_ovf_from_ova.mli \
|
|
|
97ae69 |
@@ -116,6 +127,10 @@ SOURCES_ML = \
|
|
|
97ae69 |
output_local.ml \
|
|
|
97ae69 |
output_qemu.ml \
|
|
|
97ae69 |
output_rhv.ml \
|
|
|
97ae69 |
+ output_rhv_upload_createvm_source.ml \
|
|
|
97ae69 |
+ output_rhv_upload_plugin_source.ml \
|
|
|
97ae69 |
+ output_rhv_upload_precheck_source.ml \
|
|
|
97ae69 |
+ output_rhv_upload.ml \
|
|
|
97ae69 |
output_vdsm.ml \
|
|
|
97ae69 |
inspect_source.ml \
|
|
|
97ae69 |
target_bus_assignment.ml \
|
|
|
97ae69 |
@@ -126,6 +141,15 @@ SOURCES_C = \
|
|
|
97ae69 |
libvirt_utils-c.c \
|
|
|
97ae69 |
qemuopts-c.c
|
|
|
97ae69 |
|
|
|
97ae69 |
+# These files are generated and contain rhv-upload-*.py embedded as an
|
|
|
97ae69 |
+# OCaml string.
|
|
|
97ae69 |
+output_rhv_upload_createvm_source.ml: rhv-upload-createvm.py
|
|
|
97ae69 |
+ ./embed.sh code $^ $@
|
|
|
97ae69 |
+output_rhv_upload_plugin_source.ml: rhv-upload-plugin.py
|
|
|
97ae69 |
+ ./embed.sh code $^ $@
|
|
|
97ae69 |
+output_rhv_upload_precheck_source.ml: rhv-upload-precheck.py
|
|
|
97ae69 |
+ ./embed.sh code $^ $@
|
|
|
97ae69 |
+
|
|
|
97ae69 |
if HAVE_OCAML
|
|
|
97ae69 |
|
|
|
97ae69 |
bin_PROGRAMS = virt-v2v virt-v2v-copy-to-local
|
|
|
97ae69 |
@@ -295,6 +319,7 @@ TESTS_ENVIRONMENT = $(top_builddir)/run --test
|
|
|
97ae69 |
|
|
|
97ae69 |
TESTS = \
|
|
|
97ae69 |
test-v2v-docs.sh \
|
|
|
97ae69 |
+ test-v2v-python-syntax.sh \
|
|
|
97ae69 |
test-v2v-i-ova-bad-sha1.sh \
|
|
|
97ae69 |
test-v2v-i-ova-bad-sha256.sh \
|
|
|
97ae69 |
test-v2v-i-ova-formats.sh \
|
|
|
97ae69 |
@@ -308,6 +333,7 @@ TESTS = \
|
|
|
97ae69 |
test-v2v-i-ova-two-disks.sh \
|
|
|
97ae69 |
test-v2v-i-vmx.sh \
|
|
|
97ae69 |
test-v2v-it-vddk-io-query.sh \
|
|
|
97ae69 |
+ test-v2v-o-rhv-upload-oo-query.sh \
|
|
|
97ae69 |
test-v2v-o-vdsm-oo-query.sh \
|
|
|
97ae69 |
test-v2v-bad-networks-and-bridges.sh
|
|
|
97ae69 |
|
|
|
97ae69 |
@@ -481,6 +507,7 @@ EXTRA_DIST += \
|
|
|
97ae69 |
test-v2v-o-null.sh \
|
|
|
97ae69 |
test-v2v-o-qemu.sh \
|
|
|
97ae69 |
test-v2v-o-rhv.sh \
|
|
|
97ae69 |
+ test-v2v-o-rhv-upload-oo-query.sh \
|
|
|
97ae69 |
test-v2v-o-vdsm-oo-query.sh \
|
|
|
97ae69 |
test-v2v-o-vdsm-options.sh \
|
|
|
97ae69 |
test-v2v-oa-option.sh \
|
|
|
97ae69 |
@@ -489,6 +516,7 @@ EXTRA_DIST += \
|
|
|
97ae69 |
test-v2v-print-source.expected \
|
|
|
97ae69 |
test-v2v-print-source.sh \
|
|
|
97ae69 |
test-v2v-print-source.xml \
|
|
|
97ae69 |
+ test-v2v-python-syntax.sh \
|
|
|
97ae69 |
test-v2v-conversion-of.sh \
|
|
|
97ae69 |
test-v2v-sound.sh \
|
|
|
97ae69 |
test-v2v-sound.xml \
|
|
|
97ae69 |
diff --git a/v2v/cmdline.ml b/v2v/cmdline.ml
|
|
|
97ae69 |
index c9b859dd6..6321eb656 100644
|
|
|
97ae69 |
--- a/v2v/cmdline.ml
|
|
|
97ae69 |
+++ b/v2v/cmdline.ml
|
|
|
97ae69 |
@@ -133,6 +133,8 @@ let parse_cmdline () =
|
|
|
97ae69 |
| "disk" | "local" -> output_mode := `Local
|
|
|
97ae69 |
| "null" -> output_mode := `Null
|
|
|
97ae69 |
| "ovirt" | "rhv" | "rhev" -> output_mode := `RHV
|
|
|
97ae69 |
+ | "ovirt-upload" | "ovirt_upload" | "rhv-upload" | "rhv_upload" ->
|
|
|
97ae69 |
+ output_mode := `RHV_Upload
|
|
|
97ae69 |
| "qemu" -> output_mode := `QEmu
|
|
|
97ae69 |
| "vdsm" -> output_mode := `VDSM
|
|
|
97ae69 |
| s ->
|
|
|
97ae69 |
@@ -389,6 +391,16 @@ read the man page virt-v2v(1).
|
|
|
97ae69 |
| `Null -> no_options (); `Null
|
|
|
97ae69 |
| `RHV -> no_options (); `RHV
|
|
|
97ae69 |
| `QEmu -> no_options (); `QEmu
|
|
|
97ae69 |
+ | `RHV_Upload ->
|
|
|
97ae69 |
+ if is_query then (
|
|
|
97ae69 |
+ Output_rhv_upload.print_output_options ();
|
|
|
97ae69 |
+ exit 0
|
|
|
97ae69 |
+ )
|
|
|
97ae69 |
+ else (
|
|
|
97ae69 |
+ let rhv_options =
|
|
|
97ae69 |
+ Output_rhv_upload.parse_output_options output_options in
|
|
|
97ae69 |
+ `RHV_Upload rhv_options
|
|
|
97ae69 |
+ )
|
|
|
97ae69 |
| `VDSM ->
|
|
|
97ae69 |
if is_query then (
|
|
|
97ae69 |
Output_vdsm.print_output_options ();
|
|
|
97ae69 |
@@ -571,6 +583,35 @@ read the man page virt-v2v(1).
|
|
|
97ae69 |
Output_rhv.output_rhv os output_alloc,
|
|
|
97ae69 |
output_format, output_alloc
|
|
|
97ae69 |
|
|
|
97ae69 |
+ | `RHV_Upload rhv_options ->
|
|
|
97ae69 |
+ let output_conn =
|
|
|
97ae69 |
+ match output_conn with
|
|
|
97ae69 |
+ | None ->
|
|
|
97ae69 |
+ 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")
|
|
|
97ae69 |
+ | Some oc -> oc in
|
|
|
97ae69 |
+ (* Output format / sparse must currently be raw+sparse. We can
|
|
|
97ae69 |
+ * change this in future. See TODO file for details. XXX
|
|
|
97ae69 |
+ *)
|
|
|
97ae69 |
+ if output_alloc <> Sparse || output_format <> Some "raw" then
|
|
|
97ae69 |
+ error (f_"-o rhv-upload: currently you must use ‘-of raw’ and you cannot use ‘-oa preallocated’ with this output mode. These restrictions will be loosened in a future version.");
|
|
|
97ae69 |
+ (* In theory we could make the password optional in future. *)
|
|
|
97ae69 |
+ let output_password =
|
|
|
97ae69 |
+ match output_password with
|
|
|
97ae69 |
+ | None ->
|
|
|
97ae69 |
+ 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")
|
|
|
97ae69 |
+ | Some op -> op in
|
|
|
97ae69 |
+ let os =
|
|
|
97ae69 |
+ match output_storage with
|
|
|
97ae69 |
+ | None ->
|
|
|
97ae69 |
+ error (f_"-o rhv-upload: output storage was not specified, use ‘-os’");
|
|
|
97ae69 |
+ | Some os -> os in
|
|
|
97ae69 |
+ if qemu_boot then
|
|
|
97ae69 |
+ error_option_cannot_be_used_in_output_mode "rhv-upload" "--qemu-boot";
|
|
|
97ae69 |
+ Output_rhv_upload.output_rhv_upload output_alloc output_conn
|
|
|
97ae69 |
+ output_password os
|
|
|
97ae69 |
+ rhv_options,
|
|
|
97ae69 |
+ output_format, output_alloc
|
|
|
97ae69 |
+
|
|
|
97ae69 |
| `VDSM vdsm_options ->
|
|
|
97ae69 |
if output_password <> None then
|
|
|
97ae69 |
error_option_cannot_be_used_in_output_mode "vdsm" "-op";
|
|
|
97ae69 |
diff --git a/v2v/embed.sh b/v2v/embed.sh
|
|
|
97ae69 |
new file mode 100755
|
|
|
97ae69 |
index 000000000..363d7e2b0
|
|
|
97ae69 |
--- /dev/null
|
|
|
97ae69 |
+++ b/v2v/embed.sh
|
|
|
97ae69 |
@@ -0,0 +1,48 @@
|
|
|
97ae69 |
+#!/bin/bash -
|
|
|
97ae69 |
+# Embed code or other content into an OCaml file.
|
|
|
97ae69 |
+# Copyright (C) 2018 Red Hat Inc.
|
|
|
97ae69 |
+#
|
|
|
97ae69 |
+# This program is free software; you can redistribute it and/or modify
|
|
|
97ae69 |
+# it under the terms of the GNU General Public License as published by
|
|
|
97ae69 |
+# the Free Software Foundation; either version 2 of the License, or
|
|
|
97ae69 |
+# (at your option) any later version.
|
|
|
97ae69 |
+#
|
|
|
97ae69 |
+# This program is distributed in the hope that it will be useful,
|
|
|
97ae69 |
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
97ae69 |
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
97ae69 |
+# GNU General Public License for more details.
|
|
|
97ae69 |
+#
|
|
|
97ae69 |
+# You should have received a copy of the GNU General Public License
|
|
|
97ae69 |
+# along with this program; if not, write to the Free Software
|
|
|
97ae69 |
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
|
|
97ae69 |
+
|
|
|
97ae69 |
+# Embed code or other content into an OCaml file.
|
|
|
97ae69 |
+#
|
|
|
97ae69 |
+# It is embedded into a string. As OCaml string literals have virtually
|
|
|
97ae69 |
+# no restrictions on length or content we only have to escape double
|
|
|
97ae69 |
+# quotes for backslash characters.
|
|
|
97ae69 |
+
|
|
|
97ae69 |
+if [ $# -ne 3 ]; then
|
|
|
97ae69 |
+ echo "embed.sh identifier input output"
|
|
|
97ae69 |
+ exit 1
|
|
|
97ae69 |
+fi
|
|
|
97ae69 |
+
|
|
|
97ae69 |
+set -e
|
|
|
97ae69 |
+set -u
|
|
|
97ae69 |
+
|
|
|
97ae69 |
+ident="$1"
|
|
|
97ae69 |
+input="$2"
|
|
|
97ae69 |
+output="$3"
|
|
|
97ae69 |
+
|
|
|
97ae69 |
+rm -f "$output" "$output"-t
|
|
|
97ae69 |
+
|
|
|
97ae69 |
+exec >"$output"-t
|
|
|
97ae69 |
+
|
|
|
97ae69 |
+echo "(* Generated by embed.sh from $input *)"
|
|
|
97ae69 |
+echo
|
|
|
97ae69 |
+echo let "$ident" = '"'
|
|
|
97ae69 |
+sed -e 's/\(["\]\)/\\\1/g' < "$input"
|
|
|
97ae69 |
+echo '"'
|
|
|
97ae69 |
+
|
|
|
97ae69 |
+chmod -w "$output"-t
|
|
|
97ae69 |
+mv "$output"-t "$output"
|
|
|
97ae69 |
diff --git a/v2v/output_rhv_upload.ml b/v2v/output_rhv_upload.ml
|
|
|
97ae69 |
new file mode 100644
|
|
|
97ae69 |
index 000000000..129461242
|
|
|
97ae69 |
--- /dev/null
|
|
|
97ae69 |
+++ b/v2v/output_rhv_upload.ml
|
|
|
97ae69 |
@@ -0,0 +1,401 @@
|
|
|
97ae69 |
+(* virt-v2v
|
|
|
97ae69 |
+ * Copyright (C) 2009-2018 Red Hat Inc.
|
|
|
97ae69 |
+ *
|
|
|
97ae69 |
+ * This program is free software; you can redistribute it and/or modify
|
|
|
97ae69 |
+ * it under the terms of the GNU General Public License as published by
|
|
|
97ae69 |
+ * the Free Software Foundation; either version 2 of the License, or
|
|
|
97ae69 |
+ * (at your option) any later version.
|
|
|
97ae69 |
+ *
|
|
|
97ae69 |
+ * This program is distributed in the hope that it will be useful,
|
|
|
97ae69 |
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
97ae69 |
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
97ae69 |
+ * GNU General Public License for more details.
|
|
|
97ae69 |
+ *
|
|
|
97ae69 |
+ * You should have received a copy of the GNU General Public License along
|
|
|
97ae69 |
+ * with this program; if not, write to the Free Software Foundation, Inc.,
|
|
|
97ae69 |
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
|
|
97ae69 |
+ *)
|
|
|
97ae69 |
+
|
|
|
97ae69 |
+open Printf
|
|
|
97ae69 |
+open Unix
|
|
|
97ae69 |
+
|
|
|
97ae69 |
+open Std_utils
|
|
|
97ae69 |
+open Tools_utils
|
|
|
97ae69 |
+open Unix_utils
|
|
|
97ae69 |
+open Common_gettext.Gettext
|
|
|
97ae69 |
+
|
|
|
97ae69 |
+open Types
|
|
|
97ae69 |
+open Utils
|
|
|
97ae69 |
+
|
|
|
97ae69 |
+type rhv_options = {
|
|
|
97ae69 |
+ rhv_cafile : string;
|
|
|
97ae69 |
+ rhv_cluster : string option;
|
|
|
97ae69 |
+ rhv_direct : bool;
|
|
|
97ae69 |
+ rhv_verifypeer : bool;
|
|
|
97ae69 |
+}
|
|
|
97ae69 |
+
|
|
|
97ae69 |
+let print_output_options () =
|
|
|
97ae69 |
+ printf (f_"Output options (-oo) which can be used with -o rhv-upload:
|
|
|
97ae69 |
+
|
|
|
97ae69 |
+ -oo rhv-cafile=CA.PEM Set ‘ca.pem’ certificate bundle filename.
|
|
|
97ae69 |
+ -oo rhv-cluster=CLUSTERNAME Set RHV cluster name.
|
|
|
97ae69 |
+ -oo rhv-direct[=true|false] Use direct transfer mode (default: false).
|
|
|
97ae69 |
+ -oo rhv-verifypeer[=true|false] Verify server identity (default: false).
|
|
|
97ae69 |
+")
|
|
|
97ae69 |
+
|
|
|
97ae69 |
+let parse_output_options options =
|
|
|
97ae69 |
+ let rhv_cafile = ref None in
|
|
|
97ae69 |
+ let rhv_cluster = ref None in
|
|
|
97ae69 |
+ let rhv_direct = ref false in
|
|
|
97ae69 |
+ let rhv_verifypeer = ref false in
|
|
|
97ae69 |
+
|
|
|
97ae69 |
+ List.iter (
|
|
|
97ae69 |
+ function
|
|
|
97ae69 |
+ | "rhv-cafile", v ->
|
|
|
97ae69 |
+ if !rhv_cafile <> None then
|
|
|
97ae69 |
+ error (f_"-o rhv-upload: -oo rhv-cafile set twice");
|
|
|
97ae69 |
+ rhv_cafile := Some v
|
|
|
97ae69 |
+ | "rhv-cluster", v ->
|
|
|
97ae69 |
+ if !rhv_cluster <> None then
|
|
|
97ae69 |
+ error (f_"-o rhv-upload: -oo rhv-cluster set twice");
|
|
|
97ae69 |
+ rhv_cluster := Some v
|
|
|
97ae69 |
+ | "rhv-direct", "" -> rhv_direct := true
|
|
|
97ae69 |
+ | "rhv-direct", v -> rhv_direct := bool_of_string v
|
|
|
97ae69 |
+ | "rhv-verifypeer", "" -> rhv_verifypeer := true
|
|
|
97ae69 |
+ | "rhv-verifypeer", v -> rhv_verifypeer := bool_of_string v
|
|
|
97ae69 |
+ | k, _ ->
|
|
|
97ae69 |
+ error (f_"-o rhv-upload: unknown output option ‘-oo %s’") k
|
|
|
97ae69 |
+ ) options;
|
|
|
97ae69 |
+
|
|
|
97ae69 |
+ let rhv_cafile =
|
|
|
97ae69 |
+ match !rhv_cafile with
|
|
|
97ae69 |
+ | Some s -> s
|
|
|
97ae69 |
+ | None ->
|
|
|
97ae69 |
+ error (f_"-o rhv-upload: must use ‘-oo rhv-cafile’ to supply the path to the oVirt or RHV user’s ‘ca.pem’ file") in
|
|
|
97ae69 |
+ let rhv_cluster = !rhv_cluster in
|
|
|
97ae69 |
+ let rhv_direct = !rhv_direct in
|
|
|
97ae69 |
+ let rhv_verifypeer = !rhv_verifypeer in
|
|
|
97ae69 |
+
|
|
|
97ae69 |
+ { rhv_cafile; rhv_cluster; rhv_direct; rhv_verifypeer }
|
|
|
97ae69 |
+
|
|
|
97ae69 |
+let python3 = "python3" (* Defined by PEP 394 *)
|
|
|
97ae69 |
+let pidfile_timeout = 30
|
|
|
97ae69 |
+let finalization_timeout = 5*60
|
|
|
97ae69 |
+
|
|
|
97ae69 |
+class output_rhv_upload output_alloc output_conn
|
|
|
97ae69 |
+ output_password output_storage
|
|
|
97ae69 |
+ rhv_options =
|
|
|
97ae69 |
+ (* Create a temporary directory which will be deleted on exit. *)
|
|
|
97ae69 |
+ let tmpdir =
|
|
|
97ae69 |
+ let base_dir = (open_guestfs ())#get_cachedir () in
|
|
|
97ae69 |
+ let t = Mkdtemp.temp_dir ~base_dir "rhvupload." in
|
|
|
97ae69 |
+ rmdir_on_exit t;
|
|
|
97ae69 |
+ t in
|
|
|
97ae69 |
+
|
|
|
97ae69 |
+ let diskid_file_of_id id = tmpdir // sprintf "diskid.%d" id in
|
|
|
97ae69 |
+
|
|
|
97ae69 |
+ (* Write the Python precheck, plugin and create VM to a temporary file. *)
|
|
|
97ae69 |
+ let precheck =
|
|
|
97ae69 |
+ let precheck = tmpdir // "rhv-upload-precheck.py" in
|
|
|
97ae69 |
+ with_open_out
|
|
|
97ae69 |
+ precheck
|
|
|
97ae69 |
+ (fun chan -> output_string chan Output_rhv_upload_precheck_source.code);
|
|
|
97ae69 |
+ precheck in
|
|
|
97ae69 |
+ let plugin =
|
|
|
97ae69 |
+ let plugin = tmpdir // "rhv-upload-plugin.py" in
|
|
|
97ae69 |
+ with_open_out
|
|
|
97ae69 |
+ plugin
|
|
|
97ae69 |
+ (fun chan -> output_string chan Output_rhv_upload_plugin_source.code);
|
|
|
97ae69 |
+ plugin in
|
|
|
97ae69 |
+ let createvm =
|
|
|
97ae69 |
+ let createvm = tmpdir // "rhv-upload-createvm.py" in
|
|
|
97ae69 |
+ with_open_out
|
|
|
97ae69 |
+ createvm
|
|
|
97ae69 |
+ (fun chan -> output_string chan Output_rhv_upload_createvm_source.code);
|
|
|
97ae69 |
+ createvm in
|
|
|
97ae69 |
+
|
|
|
97ae69 |
+ (* Is SELinux enabled and enforcing on the host? *)
|
|
|
97ae69 |
+ let have_selinux =
|
|
|
97ae69 |
+ 0 = Sys.command "getenforce 2>/dev/null | grep -isq Enforcing" in
|
|
|
97ae69 |
+
|
|
|
97ae69 |
+ (* Check that the Python binary is available. *)
|
|
|
97ae69 |
+ let error_unless_python_binary_on_path () =
|
|
|
97ae69 |
+ try ignore (which python3)
|
|
|
97ae69 |
+ with Executable_not_found _ ->
|
|
|
97ae69 |
+ error (f_"no python binary called ‘%s’ can be found on the $PATH")
|
|
|
97ae69 |
+ python3
|
|
|
97ae69 |
+ in
|
|
|
97ae69 |
+
|
|
|
97ae69 |
+ (* Check that nbdkit is available and new enough. *)
|
|
|
97ae69 |
+ let error_unless_nbdkit_working () =
|
|
|
97ae69 |
+ if 0 <> Sys.command "nbdkit --version >/dev/null" then
|
|
|
97ae69 |
+ 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.");
|
|
|
97ae69 |
+
|
|
|
97ae69 |
+ (* Check it's a new enough version. The latest features we
|
|
|
97ae69 |
+ * require are ‘--exit-with-parent’ and ‘--selinux-label’, both
|
|
|
97ae69 |
+ * added in 1.1.14. (We use 1.1.16 as the minimum here because
|
|
|
97ae69 |
+ * it also adds the selinux=yes|no flag in --dump-config).
|
|
|
97ae69 |
+ *)
|
|
|
97ae69 |
+ let lines = external_command "nbdkit --help" in
|
|
|
97ae69 |
+ let lines = String.concat " " lines in
|
|
|
97ae69 |
+ if String.find lines "exit-with-parent" == -1 ||
|
|
|
97ae69 |
+ String.find lines "selinux-label" == -1 then
|
|
|
97ae69 |
+ error (f_"nbdkit is not new enough, you need to upgrade to nbdkit ≥ 1.1.16")
|
|
|
97ae69 |
+ in
|
|
|
97ae69 |
+
|
|
|
97ae69 |
+ (* Check that the python3 plugin is installed and working
|
|
|
97ae69 |
+ * and can load the plugin script.
|
|
|
97ae69 |
+ *)
|
|
|
97ae69 |
+ let error_unless_nbdkit_python3_working () =
|
|
|
97ae69 |
+ let cmd = sprintf "nbdkit %s %s --dump-plugin >/dev/null"
|
|
|
97ae69 |
+ python3 (quote plugin) in
|
|
|
97ae69 |
+ if Sys.command cmd <> 0 then
|
|
|
97ae69 |
+ error (f_"nbdkit Python 3 plugin is not installed or not working. It is required if you want to use ‘-o rhv-upload’.
|
|
|
97ae69 |
+
|
|
|
97ae69 |
+See also \"OUTPUT TO RHV\" in the virt-v2v(1) manual.")
|
|
|
97ae69 |
+ in
|
|
|
97ae69 |
+
|
|
|
97ae69 |
+ (* Check that nbdkit was compiled with SELinux support (for the
|
|
|
97ae69 |
+ * --selinux-label option).
|
|
|
97ae69 |
+ *)
|
|
|
97ae69 |
+ let error_unless_nbdkit_compiled_with_selinux () =
|
|
|
97ae69 |
+ let lines = external_command "nbdkit --dump-config" in
|
|
|
97ae69 |
+ (* In nbdkit <= 1.1.15 the selinux attribute was not present
|
|
|
97ae69 |
+ * at all in --dump-config output so there was no way to tell.
|
|
|
97ae69 |
+ * Ignore this case because there will be an error later when
|
|
|
97ae69 |
+ * we try to use the --selinux-label parameter.
|
|
|
97ae69 |
+ *)
|
|
|
97ae69 |
+ if List.mem "selinux=no" (List.map String.trim lines) then
|
|
|
97ae69 |
+ 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.")
|
|
|
97ae69 |
+ in
|
|
|
97ae69 |
+
|
|
|
97ae69 |
+ (* JSON parameters which are invariant between disks. *)
|
|
|
97ae69 |
+ let json_params = [
|
|
|
97ae69 |
+ "verbose", JSON.Bool (verbose ());
|
|
|
97ae69 |
+
|
|
|
97ae69 |
+ "output_conn", JSON.String output_conn;
|
|
|
97ae69 |
+ "output_password", JSON.String output_password;
|
|
|
97ae69 |
+ "output_storage", JSON.String output_storage;
|
|
|
97ae69 |
+ "output_sparse", JSON.Bool (match output_alloc with
|
|
|
97ae69 |
+ | Sparse -> true
|
|
|
97ae69 |
+ | Preallocated -> false);
|
|
|
97ae69 |
+ "rhv_cafile", JSON.String rhv_options.rhv_cafile;
|
|
|
97ae69 |
+ "rhv_cluster",
|
|
|
97ae69 |
+ JSON.String (Option.default "Default" rhv_options.rhv_cluster);
|
|
|
97ae69 |
+ "rhv_direct", JSON.Bool rhv_options.rhv_direct;
|
|
|
97ae69 |
+
|
|
|
97ae69 |
+ (* The 'Insecure' flag seems to be a number with various possible
|
|
|
97ae69 |
+ * meanings, however we just set it to True/False.
|
|
|
97ae69 |
+ *
|
|
|
97ae69 |
+ * https://github.com/oVirt/ovirt-engine-sdk/blob/19aa7070b80e60a4cfd910448287aecf9083acbe/sdk/lib/ovirtsdk4/__init__.py#L395
|
|
|
97ae69 |
+ *)
|
|
|
97ae69 |
+ "insecure", JSON.Bool (not rhv_options.rhv_verifypeer);
|
|
|
97ae69 |
+ ] in
|
|
|
97ae69 |
+
|
|
|
97ae69 |
+ (* nbdkit command line args which are invariant between disks. *)
|
|
|
97ae69 |
+ let nbdkit_args =
|
|
|
97ae69 |
+ let args = [
|
|
|
97ae69 |
+ "nbdkit";
|
|
|
97ae69 |
+
|
|
|
97ae69 |
+ "--foreground"; (* run in foreground *)
|
|
|
97ae69 |
+ "--exit-with-parent"; (* exit when virt-v2v exits *)
|
|
|
97ae69 |
+ "--newstyle"; (* use newstyle NBD protocol *)
|
|
|
97ae69 |
+ "--exportname"; "/";
|
|
|
97ae69 |
+
|
|
|
97ae69 |
+ "python3"; (* use the nbdkit Python 3 plugin *)
|
|
|
97ae69 |
+ plugin; (* Python plugin script *)
|
|
|
97ae69 |
+ ] in
|
|
|
97ae69 |
+ let args = if verbose () then args @ ["--verbose"] else args in
|
|
|
97ae69 |
+ let args =
|
|
|
97ae69 |
+ (* label the socket so qemu can open it *)
|
|
|
97ae69 |
+ if have_selinux then
|
|
|
97ae69 |
+ args @ ["--selinux-label"; "system_u:object_r:svirt_t:s0"]
|
|
|
97ae69 |
+ else args in
|
|
|
97ae69 |
+ args in
|
|
|
97ae69 |
+
|
|
|
97ae69 |
+object
|
|
|
97ae69 |
+ inherit output
|
|
|
97ae69 |
+
|
|
|
97ae69 |
+ method precheck () =
|
|
|
97ae69 |
+ error_unless_python_binary_on_path ();
|
|
|
97ae69 |
+ error_unless_nbdkit_working ();
|
|
|
97ae69 |
+ error_unless_nbdkit_python3_working ();
|
|
|
97ae69 |
+ if have_selinux then
|
|
|
97ae69 |
+ error_unless_nbdkit_compiled_with_selinux ()
|
|
|
97ae69 |
+
|
|
|
97ae69 |
+ method as_options =
|
|
|
97ae69 |
+ "-o rhv-upload" ^
|
|
|
97ae69 |
+ (match output_alloc with
|
|
|
97ae69 |
+ | Sparse -> "" (* default, don't need to print it *)
|
|
|
97ae69 |
+ | Preallocated -> " -oa preallocated") ^
|
|
|
97ae69 |
+ sprintf " -oc %s -op %s -os %s"
|
|
|
97ae69 |
+ output_conn output_password output_storage
|
|
|
97ae69 |
+
|
|
|
97ae69 |
+ method supported_firmware = [ TargetBIOS ]
|
|
|
97ae69 |
+
|
|
|
97ae69 |
+ method prepare_targets source targets =
|
|
|
97ae69 |
+ let output_name = source.s_name in
|
|
|
97ae69 |
+ let json_params =
|
|
|
97ae69 |
+ ("output_name", JSON.String output_name) :: json_params in
|
|
|
97ae69 |
+
|
|
|
97ae69 |
+ (* Python code prechecks. These can't run in #precheck because
|
|
|
97ae69 |
+ * we need to know the name of the virtual machine.
|
|
|
97ae69 |
+ *)
|
|
|
97ae69 |
+ let json_param_file = tmpdir // "params.json" in
|
|
|
97ae69 |
+ with_open_out
|
|
|
97ae69 |
+ json_param_file
|
|
|
97ae69 |
+ (fun chan -> output_string chan (JSON.string_of_doc json_params));
|
|
|
97ae69 |
+ if run_command [ python3; precheck; json_param_file ] <> 0 then
|
|
|
97ae69 |
+ error (f_"failed server prechecks, see earlier errors");
|
|
|
97ae69 |
+
|
|
|
97ae69 |
+ (* Create an nbdkit instance for each disk and set the
|
|
|
97ae69 |
+ * target URI to point to the NBD socket.
|
|
|
97ae69 |
+ *)
|
|
|
97ae69 |
+ List.map (
|
|
|
97ae69 |
+ fun t ->
|
|
|
97ae69 |
+ let id = t.target_overlay.ov_source.s_disk_id in
|
|
|
97ae69 |
+ let disk_name = sprintf "%s-%03d" output_name id in
|
|
|
97ae69 |
+ let json_params =
|
|
|
97ae69 |
+ ("disk_name", JSON.String disk_name) :: json_params in
|
|
|
97ae69 |
+
|
|
|
97ae69 |
+ let disk_format =
|
|
|
97ae69 |
+ match t.target_format with
|
|
|
97ae69 |
+ | ("raw" | "qcow2") as fmt -> fmt
|
|
|
97ae69 |
+ | _ ->
|
|
|
97ae69 |
+ 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.")
|
|
|
97ae69 |
+ t.target_format in
|
|
|
97ae69 |
+ let json_params =
|
|
|
97ae69 |
+ ("disk_format", JSON.String disk_format) :: json_params in
|
|
|
97ae69 |
+
|
|
|
97ae69 |
+ let disk_size = t.target_overlay.ov_virtual_size in
|
|
|
97ae69 |
+ let json_params =
|
|
|
97ae69 |
+ ("disk_size", JSON.Int64 disk_size) :: json_params in
|
|
|
97ae69 |
+
|
|
|
97ae69 |
+ (* Ask the plugin to write the disk ID to a special file. *)
|
|
|
97ae69 |
+ let diskid_file = diskid_file_of_id id in
|
|
|
97ae69 |
+ let json_params =
|
|
|
97ae69 |
+ ("diskid_file", JSON.String diskid_file) :: json_params in
|
|
|
97ae69 |
+
|
|
|
97ae69 |
+ (* Write the JSON parameters to a file. *)
|
|
|
97ae69 |
+ let json_param_file = tmpdir // sprintf "params%d.json" id in
|
|
|
97ae69 |
+ with_open_out
|
|
|
97ae69 |
+ json_param_file
|
|
|
97ae69 |
+ (fun chan -> output_string chan (JSON.string_of_doc json_params));
|
|
|
97ae69 |
+
|
|
|
97ae69 |
+ let sock = tmpdir // sprintf "nbdkit%d.sock" id in
|
|
|
97ae69 |
+ let pidfile = tmpdir // sprintf "nbdkit%d.pid" id in
|
|
|
97ae69 |
+
|
|
|
97ae69 |
+ (* Add common arguments to per-target arguments. *)
|
|
|
97ae69 |
+ let args =
|
|
|
97ae69 |
+ nbdkit_args @ [ "--pidfile"; pidfile;
|
|
|
97ae69 |
+ "--unix"; sock;
|
|
|
97ae69 |
+ sprintf "params=%s" json_param_file ] in
|
|
|
97ae69 |
+
|
|
|
97ae69 |
+ (* Print the full command we are about to run when debugging. *)
|
|
|
97ae69 |
+ if verbose () then (
|
|
|
97ae69 |
+ eprintf "running nbdkit:\n";
|
|
|
97ae69 |
+ List.iter (fun arg -> eprintf " %s" (quote arg)) args;
|
|
|
97ae69 |
+ prerr_newline ()
|
|
|
97ae69 |
+ );
|
|
|
97ae69 |
+
|
|
|
97ae69 |
+ (* Start an nbdkit instance in the background. By using
|
|
|
97ae69 |
+ * --exit-with-parent we don't have to worry about clean-up.
|
|
|
97ae69 |
+ *)
|
|
|
97ae69 |
+ let args = Array.of_list args in
|
|
|
97ae69 |
+ let pid = fork () in
|
|
|
97ae69 |
+ if pid = 0 then (
|
|
|
97ae69 |
+ (* Child process (nbdkit). *)
|
|
|
97ae69 |
+ execvp "nbdkit" args
|
|
|
97ae69 |
+ );
|
|
|
97ae69 |
+
|
|
|
97ae69 |
+ (* Wait for the pidfile to appear so we know that nbdkit
|
|
|
97ae69 |
+ * is listening for requests.
|
|
|
97ae69 |
+ *)
|
|
|
97ae69 |
+ if not (wait_for_file pidfile pidfile_timeout) then (
|
|
|
97ae69 |
+ if verbose () then
|
|
|
97ae69 |
+ error (f_"nbdkit did not start up. See previous debugging messages for problems.")
|
|
|
97ae69 |
+ else
|
|
|
97ae69 |
+ error (f_"nbdkit did not start up. There may be errors printed by nbdkit above.
|
|
|
97ae69 |
+
|
|
|
97ae69 |
+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.")
|
|
|
97ae69 |
+ );
|
|
|
97ae69 |
+
|
|
|
97ae69 |
+ if have_selinux then (
|
|
|
97ae69 |
+ (* Note that Unix domain sockets have both a file label and
|
|
|
97ae69 |
+ * a socket/process label. Using --selinux-label above
|
|
|
97ae69 |
+ * only set the socket label, but we must also set the file
|
|
|
97ae69 |
+ * label.
|
|
|
97ae69 |
+ *)
|
|
|
97ae69 |
+ ignore (
|
|
|
97ae69 |
+ run_command ["chcon"; "system_u:object_r:svirt_image_t:s0";
|
|
|
97ae69 |
+ sock]
|
|
|
97ae69 |
+ );
|
|
|
97ae69 |
+ );
|
|
|
97ae69 |
+ (* ... and the regular Unix permissions, in case qemu is
|
|
|
97ae69 |
+ * running as another user.
|
|
|
97ae69 |
+ *)
|
|
|
97ae69 |
+ chmod sock 0o777;
|
|
|
97ae69 |
+
|
|
|
97ae69 |
+ (* Tell ‘qemu-img convert’ to write to the nbd socket which is
|
|
|
97ae69 |
+ * connected to nbdkit.
|
|
|
97ae69 |
+ *)
|
|
|
97ae69 |
+ let json_params = [
|
|
|
97ae69 |
+ "file.driver", JSON.String "nbd";
|
|
|
97ae69 |
+ "file.path", JSON.String sock;
|
|
|
97ae69 |
+ "file.export", JSON.String "/";
|
|
|
97ae69 |
+ ] in
|
|
|
97ae69 |
+ let target_file =
|
|
|
97ae69 |
+ TargetURI ("json:" ^ JSON.string_of_doc json_params) in
|
|
|
97ae69 |
+ { t with target_file }
|
|
|
97ae69 |
+ ) targets
|
|
|
97ae69 |
+
|
|
|
97ae69 |
+ method create_metadata source targets _ guestcaps inspect target_firmware =
|
|
|
97ae69 |
+ (* Get the UUIDs of each disk image. These files are written
|
|
|
97ae69 |
+ * out by the nbdkit plugins on successful finalization of the
|
|
|
97ae69 |
+ * transfer.
|
|
|
97ae69 |
+ *)
|
|
|
97ae69 |
+ let nr_disks = List.length targets in
|
|
|
97ae69 |
+ let image_uuids =
|
|
|
97ae69 |
+ List.map (
|
|
|
97ae69 |
+ fun t ->
|
|
|
97ae69 |
+ let id = t.target_overlay.ov_source.s_disk_id in
|
|
|
97ae69 |
+ let diskid_file = diskid_file_of_id id in
|
|
|
97ae69 |
+ if not (wait_for_file diskid_file finalization_timeout) then
|
|
|
97ae69 |
+ error (f_"transfer of disk %d/%d failed, see earlier error messages")
|
|
|
97ae69 |
+ (id+1) nr_disks;
|
|
|
97ae69 |
+ let diskid = read_whole_file diskid_file in
|
|
|
97ae69 |
+ diskid
|
|
|
97ae69 |
+ ) targets in
|
|
|
97ae69 |
+
|
|
|
97ae69 |
+ (* We don't have the storage domain UUID, but instead we write
|
|
|
97ae69 |
+ * in a magic value which the Python code (which can get it)
|
|
|
97ae69 |
+ * will substitute.
|
|
|
97ae69 |
+ *)
|
|
|
97ae69 |
+ let sd_uuid = "@SD_UUID@" in
|
|
|
97ae69 |
+
|
|
|
97ae69 |
+ (* The volume and VM UUIDs are made up. *)
|
|
|
97ae69 |
+ let vol_uuids = List.map (fun _ -> uuidgen ()) targets
|
|
|
97ae69 |
+ and vm_uuid = uuidgen () in
|
|
|
97ae69 |
+
|
|
|
97ae69 |
+ (* Create the metadata. *)
|
|
|
97ae69 |
+ let ovf =
|
|
|
97ae69 |
+ Create_ovf.create_ovf source targets guestcaps inspect
|
|
|
97ae69 |
+ output_alloc
|
|
|
97ae69 |
+ sd_uuid image_uuids vol_uuids vm_uuid
|
|
|
97ae69 |
+ OVirt in
|
|
|
97ae69 |
+ let ovf = DOM.doc_to_string ovf in
|
|
|
97ae69 |
+
|
|
|
97ae69 |
+ let json_param_file = tmpdir // "params.json" in
|
|
|
97ae69 |
+ with_open_out
|
|
|
97ae69 |
+ json_param_file
|
|
|
97ae69 |
+ (fun chan -> output_string chan (JSON.string_of_doc json_params));
|
|
|
97ae69 |
+
|
|
|
97ae69 |
+ let ovf_file = tmpdir // "vm.ovf" in
|
|
|
97ae69 |
+ with_open_out ovf_file (fun chan -> output_string chan ovf);
|
|
|
97ae69 |
+ if run_command [ python3; createvm; json_param_file; ovf_file ] <> 0 then
|
|
|
97ae69 |
+ error (f_"failed to create virtual machine, see earlier errors")
|
|
|
97ae69 |
+
|
|
|
97ae69 |
+end
|
|
|
97ae69 |
+
|
|
|
97ae69 |
+let output_rhv_upload = new output_rhv_upload
|
|
|
97ae69 |
+let () = Modules_list.register_output_module "rhv-upload"
|
|
|
97ae69 |
diff --git a/v2v/output_rhv_upload.mli b/v2v/output_rhv_upload.mli
|
|
|
97ae69 |
new file mode 100644
|
|
|
97ae69 |
index 000000000..f6cd69a61
|
|
|
97ae69 |
--- /dev/null
|
|
|
97ae69 |
+++ b/v2v/output_rhv_upload.mli
|
|
|
97ae69 |
@@ -0,0 +1,33 @@
|
|
|
97ae69 |
+(* virt-v2v
|
|
|
97ae69 |
+ * Copyright (C) 2009-2018 Red Hat Inc.
|
|
|
97ae69 |
+ *
|
|
|
97ae69 |
+ * This program is free software; you can redistribute it and/or modify
|
|
|
97ae69 |
+ * it under the terms of the GNU General Public License as published by
|
|
|
97ae69 |
+ * the Free Software Foundation; either version 2 of the License, or
|
|
|
97ae69 |
+ * (at your option) any later version.
|
|
|
97ae69 |
+ *
|
|
|
97ae69 |
+ * This program is distributed in the hope that it will be useful,
|
|
|
97ae69 |
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
97ae69 |
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
97ae69 |
+ * GNU General Public License for more details.
|
|
|
97ae69 |
+ *
|
|
|
97ae69 |
+ * You should have received a copy of the GNU General Public License along
|
|
|
97ae69 |
+ * with this program; if not, write to the Free Software Foundation, Inc.,
|
|
|
97ae69 |
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
|
|
97ae69 |
+ *)
|
|
|
97ae69 |
+
|
|
|
97ae69 |
+(** [-o rhv-upload] target. *)
|
|
|
97ae69 |
+
|
|
|
97ae69 |
+type rhv_options
|
|
|
97ae69 |
+(** Miscellaneous extra command line parameters used by rhv-upload. *)
|
|
|
97ae69 |
+
|
|
|
97ae69 |
+val print_output_options : unit -> unit
|
|
|
97ae69 |
+val parse_output_options : (string * string) list -> rhv_options
|
|
|
97ae69 |
+(** Print and parse rhv-upload -oo options. *)
|
|
|
97ae69 |
+
|
|
|
97ae69 |
+val output_rhv_upload : Types.output_allocation -> string -> string ->
|
|
|
97ae69 |
+ string -> rhv_options -> Types.output
|
|
|
97ae69 |
+(** [output_rhv_upload output_alloc output_conn output_password output_storage
|
|
|
97ae69 |
+ rhv_options]
|
|
|
97ae69 |
+ creates and returns a new {!Types.output} object specialized for writing
|
|
|
97ae69 |
+ output to oVirt or RHV directly via RHV APIs. *)
|
|
|
97ae69 |
diff --git a/v2v/output_rhv_upload_createvm_source.mli b/v2v/output_rhv_upload_createvm_source.mli
|
|
|
97ae69 |
new file mode 100644
|
|
|
97ae69 |
index 000000000..c1bafa15b
|
|
|
97ae69 |
--- /dev/null
|
|
|
97ae69 |
+++ b/v2v/output_rhv_upload_createvm_source.mli
|
|
|
97ae69 |
@@ -0,0 +1,19 @@
|
|
|
97ae69 |
+(* virt-v2v
|
|
|
97ae69 |
+ * Copyright (C) 2018 Red Hat Inc.
|
|
|
97ae69 |
+ *
|
|
|
97ae69 |
+ * This program is free software; you can redistribute it and/or modify
|
|
|
97ae69 |
+ * it under the terms of the GNU General Public License as published by
|
|
|
97ae69 |
+ * the Free Software Foundation; either version 2 of the License, or
|
|
|
97ae69 |
+ * (at your option) any later version.
|
|
|
97ae69 |
+ *
|
|
|
97ae69 |
+ * This program is distributed in the hope that it will be useful,
|
|
|
97ae69 |
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
97ae69 |
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
97ae69 |
+ * GNU General Public License for more details.
|
|
|
97ae69 |
+ *
|
|
|
97ae69 |
+ * You should have received a copy of the GNU General Public License along
|
|
|
97ae69 |
+ * with this program; if not, write to the Free Software Foundation, Inc.,
|
|
|
97ae69 |
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
|
|
97ae69 |
+ *)
|
|
|
97ae69 |
+
|
|
|
97ae69 |
+val code : string
|
|
|
97ae69 |
diff --git a/v2v/output_rhv_upload_plugin_source.mli b/v2v/output_rhv_upload_plugin_source.mli
|
|
|
97ae69 |
new file mode 100644
|
|
|
97ae69 |
index 000000000..c1bafa15b
|
|
|
97ae69 |
--- /dev/null
|
|
|
97ae69 |
+++ b/v2v/output_rhv_upload_plugin_source.mli
|
|
|
97ae69 |
@@ -0,0 +1,19 @@
|
|
|
97ae69 |
+(* virt-v2v
|
|
|
97ae69 |
+ * Copyright (C) 2018 Red Hat Inc.
|
|
|
97ae69 |
+ *
|
|
|
97ae69 |
+ * This program is free software; you can redistribute it and/or modify
|
|
|
97ae69 |
+ * it under the terms of the GNU General Public License as published by
|
|
|
97ae69 |
+ * the Free Software Foundation; either version 2 of the License, or
|
|
|
97ae69 |
+ * (at your option) any later version.
|
|
|
97ae69 |
+ *
|
|
|
97ae69 |
+ * This program is distributed in the hope that it will be useful,
|
|
|
97ae69 |
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
97ae69 |
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
97ae69 |
+ * GNU General Public License for more details.
|
|
|
97ae69 |
+ *
|
|
|
97ae69 |
+ * You should have received a copy of the GNU General Public License along
|
|
|
97ae69 |
+ * with this program; if not, write to the Free Software Foundation, Inc.,
|
|
|
97ae69 |
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
|
|
97ae69 |
+ *)
|
|
|
97ae69 |
+
|
|
|
97ae69 |
+val code : string
|
|
|
97ae69 |
diff --git a/v2v/output_rhv_upload_precheck_source.mli b/v2v/output_rhv_upload_precheck_source.mli
|
|
|
97ae69 |
new file mode 100644
|
|
|
97ae69 |
index 000000000..c1bafa15b
|
|
|
97ae69 |
--- /dev/null
|
|
|
97ae69 |
+++ b/v2v/output_rhv_upload_precheck_source.mli
|
|
|
97ae69 |
@@ -0,0 +1,19 @@
|
|
|
97ae69 |
+(* virt-v2v
|
|
|
97ae69 |
+ * Copyright (C) 2018 Red Hat Inc.
|
|
|
97ae69 |
+ *
|
|
|
97ae69 |
+ * This program is free software; you can redistribute it and/or modify
|
|
|
97ae69 |
+ * it under the terms of the GNU General Public License as published by
|
|
|
97ae69 |
+ * the Free Software Foundation; either version 2 of the License, or
|
|
|
97ae69 |
+ * (at your option) any later version.
|
|
|
97ae69 |
+ *
|
|
|
97ae69 |
+ * This program is distributed in the hope that it will be useful,
|
|
|
97ae69 |
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
97ae69 |
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
97ae69 |
+ * GNU General Public License for more details.
|
|
|
97ae69 |
+ *
|
|
|
97ae69 |
+ * You should have received a copy of the GNU General Public License along
|
|
|
97ae69 |
+ * with this program; if not, write to the Free Software Foundation, Inc.,
|
|
|
97ae69 |
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
|
|
97ae69 |
+ *)
|
|
|
97ae69 |
+
|
|
|
97ae69 |
+val code : string
|
|
|
97ae69 |
diff --git a/v2v/rhv-upload-createvm.py b/v2v/rhv-upload-createvm.py
|
|
|
97ae69 |
new file mode 100644
|
|
|
97ae69 |
index 000000000..a34627ec8
|
|
|
97ae69 |
--- /dev/null
|
|
|
97ae69 |
+++ b/v2v/rhv-upload-createvm.py
|
|
|
97ae69 |
@@ -0,0 +1,86 @@
|
|
|
97ae69 |
+# -*- python -*-
|
|
|
97ae69 |
+# oVirt or RHV upload create VM used by ‘virt-v2v -o rhv-upload’
|
|
|
97ae69 |
+# Copyright (C) 2018 Red Hat Inc.
|
|
|
97ae69 |
+#
|
|
|
97ae69 |
+# This program is free software; you can redistribute it and/or modify
|
|
|
97ae69 |
+# it under the terms of the GNU General Public License as published by
|
|
|
97ae69 |
+# the Free Software Foundation; either version 2 of the License, or
|
|
|
97ae69 |
+# (at your option) any later version.
|
|
|
97ae69 |
+#
|
|
|
97ae69 |
+# This program is distributed in the hope that it will be useful,
|
|
|
97ae69 |
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
97ae69 |
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
97ae69 |
+# GNU General Public License for more details.
|
|
|
97ae69 |
+#
|
|
|
97ae69 |
+# You should have received a copy of the GNU General Public License along
|
|
|
97ae69 |
+# with this program; if not, write to the Free Software Foundation, Inc.,
|
|
|
97ae69 |
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
|
|
97ae69 |
+
|
|
|
97ae69 |
+import json
|
|
|
97ae69 |
+import logging
|
|
|
97ae69 |
+import sys
|
|
|
97ae69 |
+import time
|
|
|
97ae69 |
+
|
|
|
97ae69 |
+from http.client import HTTPSConnection
|
|
|
97ae69 |
+from urllib.parse import urlparse
|
|
|
97ae69 |
+
|
|
|
97ae69 |
+import ovirtsdk4 as sdk
|
|
|
97ae69 |
+import ovirtsdk4.types as types
|
|
|
97ae69 |
+
|
|
|
97ae69 |
+# Parameters are passed in via a JSON doc from the OCaml code.
|
|
|
97ae69 |
+# Because this Python code ships embedded inside virt-v2v there
|
|
|
97ae69 |
+# is no formal API here.
|
|
|
97ae69 |
+params = None
|
|
|
97ae69 |
+ovf = None # OVF file
|
|
|
97ae69 |
+
|
|
|
97ae69 |
+if len(sys.argv) != 3:
|
|
|
97ae69 |
+ raise RuntimeError("incorrect number of parameters")
|
|
|
97ae69 |
+
|
|
|
97ae69 |
+# Parameters are passed in via a JSON document.
|
|
|
97ae69 |
+with open(sys.argv[1], 'r') as fp:
|
|
|
97ae69 |
+ params = json.load(fp)
|
|
|
97ae69 |
+
|
|
|
97ae69 |
+# What is passed in is a password file, read the actual password.
|
|
|
97ae69 |
+with open(params['output_password'], 'r') as fp:
|
|
|
97ae69 |
+ output_password = fp.read()
|
|
|
97ae69 |
+output_password = output_password.rstrip()
|
|
|
97ae69 |
+
|
|
|
97ae69 |
+# Read the OVF document.
|
|
|
97ae69 |
+with open(sys.argv[2], 'r') as fp:
|
|
|
97ae69 |
+ ovf = fp.read()
|
|
|
97ae69 |
+
|
|
|
97ae69 |
+# Parse out the username from the output_conn URL.
|
|
|
97ae69 |
+parsed = urlparse(params['output_conn'])
|
|
|
97ae69 |
+username = parsed.username or "admin@internal"
|
|
|
97ae69 |
+
|
|
|
97ae69 |
+# Connect to the server.
|
|
|
97ae69 |
+connection = sdk.Connection(
|
|
|
97ae69 |
+ url = params['output_conn'],
|
|
|
97ae69 |
+ username = username,
|
|
|
97ae69 |
+ password = output_password,
|
|
|
97ae69 |
+ ca_file = params['rhv_cafile'],
|
|
|
97ae69 |
+ log = logging.getLogger(),
|
|
|
97ae69 |
+ insecure = params['insecure'],
|
|
|
97ae69 |
+)
|
|
|
97ae69 |
+
|
|
|
97ae69 |
+system_service = connection.system_service()
|
|
|
97ae69 |
+
|
|
|
97ae69 |
+# Get the storage domain UUID and substitute it into the OVF doc.
|
|
|
97ae69 |
+sds_service = system_service.storage_domains_service()
|
|
|
97ae69 |
+sd = sds_service.list(search=("name=%s" % params['output_storage']))[0]
|
|
|
97ae69 |
+sd_uuid = sd.id
|
|
|
97ae69 |
+
|
|
|
97ae69 |
+ovf.replace("@SD_UUID@", sd_uuid)
|
|
|
97ae69 |
+
|
|
|
97ae69 |
+vms_service = system_service.vms_service()
|
|
|
97ae69 |
+vm = vms_service.add(
|
|
|
97ae69 |
+ types.Vm(
|
|
|
97ae69 |
+ cluster=types.Cluster(name = params['rhv_cluster']),
|
|
|
97ae69 |
+ initialization=types.Initialization(
|
|
|
97ae69 |
+ configuration = types.Configuration(
|
|
|
97ae69 |
+ type = types.ConfigurationType.OVA,
|
|
|
97ae69 |
+ data = ovf,
|
|
|
97ae69 |
+ )
|
|
|
97ae69 |
+ )
|
|
|
97ae69 |
+ )
|
|
|
97ae69 |
+)
|
|
|
97ae69 |
diff --git a/v2v/rhv-upload-plugin.py b/v2v/rhv-upload-plugin.py
|
|
|
97ae69 |
new file mode 100644
|
|
|
97ae69 |
index 000000000..791c9e7d2
|
|
|
97ae69 |
--- /dev/null
|
|
|
97ae69 |
+++ b/v2v/rhv-upload-plugin.py
|
|
|
97ae69 |
@@ -0,0 +1,445 @@
|
|
|
97ae69 |
+# -*- python -*-
|
|
|
97ae69 |
+# oVirt or RHV upload nbdkit plugin used by ‘virt-v2v -o rhv-upload’
|
|
|
97ae69 |
+# Copyright (C) 2018 Red Hat Inc.
|
|
|
97ae69 |
+#
|
|
|
97ae69 |
+# This program is free software; you can redistribute it and/or modify
|
|
|
97ae69 |
+# it under the terms of the GNU General Public License as published by
|
|
|
97ae69 |
+# the Free Software Foundation; either version 2 of the License, or
|
|
|
97ae69 |
+# (at your option) any later version.
|
|
|
97ae69 |
+#
|
|
|
97ae69 |
+# This program is distributed in the hope that it will be useful,
|
|
|
97ae69 |
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
97ae69 |
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
97ae69 |
+# GNU General Public License for more details.
|
|
|
97ae69 |
+#
|
|
|
97ae69 |
+# You should have received a copy of the GNU General Public License along
|
|
|
97ae69 |
+# with this program; if not, write to the Free Software Foundation, Inc.,
|
|
|
97ae69 |
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
|
|
97ae69 |
+
|
|
|
97ae69 |
+import builtins
|
|
|
97ae69 |
+import json
|
|
|
97ae69 |
+import logging
|
|
|
97ae69 |
+import ssl
|
|
|
97ae69 |
+import sys
|
|
|
97ae69 |
+import time
|
|
|
97ae69 |
+
|
|
|
97ae69 |
+from http.client import HTTPSConnection
|
|
|
97ae69 |
+from urllib.parse import urlparse
|
|
|
97ae69 |
+
|
|
|
97ae69 |
+import ovirtsdk4 as sdk
|
|
|
97ae69 |
+import ovirtsdk4.types as types
|
|
|
97ae69 |
+
|
|
|
97ae69 |
+# Timeout to wait for oVirt disks to change status, or the transfer
|
|
|
97ae69 |
+# object to finish initializing [seconds].
|
|
|
97ae69 |
+timeout = 5*60
|
|
|
97ae69 |
+
|
|
|
97ae69 |
+# Parameters are passed in via a JSON doc from the OCaml code.
|
|
|
97ae69 |
+# Because this Python code ships embedded inside virt-v2v there
|
|
|
97ae69 |
+# is no formal API here.
|
|
|
97ae69 |
+params = None
|
|
|
97ae69 |
+
|
|
|
97ae69 |
+def config(key, value):
|
|
|
97ae69 |
+ global params
|
|
|
97ae69 |
+
|
|
|
97ae69 |
+ if key == "params":
|
|
|
97ae69 |
+ with builtins.open(value, 'r') as fp:
|
|
|
97ae69 |
+ params = json.load(fp)
|
|
|
97ae69 |
+ else:
|
|
|
97ae69 |
+ raise RuntimeError("unknown configuration key '%s'" % key)
|
|
|
97ae69 |
+
|
|
|
97ae69 |
+def config_complete():
|
|
|
97ae69 |
+ if params is None:
|
|
|
97ae69 |
+ raise RuntimeError("missing configuration parameters")
|
|
|
97ae69 |
+
|
|
|
97ae69 |
+def debug(s):
|
|
|
97ae69 |
+ if params['verbose']:
|
|
|
97ae69 |
+ print(s, file=sys.stderr)
|
|
|
97ae69 |
+ sys.stderr.flush()
|
|
|
97ae69 |
+
|
|
|
97ae69 |
+def open(readonly):
|
|
|
97ae69 |
+ # Parse out the username from the output_conn URL.
|
|
|
97ae69 |
+ parsed = urlparse(params['output_conn'])
|
|
|
97ae69 |
+ username = parsed.username or "admin@internal"
|
|
|
97ae69 |
+
|
|
|
97ae69 |
+ # Read the password from file.
|
|
|
97ae69 |
+ with builtins.open(params['output_password'], 'r') as fp:
|
|
|
97ae69 |
+ password = fp.read()
|
|
|
97ae69 |
+ password = password.rstrip()
|
|
|
97ae69 |
+
|
|
|
97ae69 |
+ # Connect to the server.
|
|
|
97ae69 |
+ connection = sdk.Connection(
|
|
|
97ae69 |
+ url = params['output_conn'],
|
|
|
97ae69 |
+ username = username,
|
|
|
97ae69 |
+ password = password,
|
|
|
97ae69 |
+ ca_file = params['rhv_cafile'],
|
|
|
97ae69 |
+ log = logging.getLogger(),
|
|
|
97ae69 |
+ insecure = params['insecure'],
|
|
|
97ae69 |
+ )
|
|
|
97ae69 |
+
|
|
|
97ae69 |
+ system_service = connection.system_service()
|
|
|
97ae69 |
+
|
|
|
97ae69 |
+ # Create the disk.
|
|
|
97ae69 |
+ disks_service = system_service.disks_service()
|
|
|
97ae69 |
+ if params['disk_format'] == "raw":
|
|
|
97ae69 |
+ disk_format = types.DiskFormat.RAW
|
|
|
97ae69 |
+ else:
|
|
|
97ae69 |
+ disk_format = types.DiskFormat.COW
|
|
|
97ae69 |
+ disk = disks_service.add(
|
|
|
97ae69 |
+ disk = types.Disk(
|
|
|
97ae69 |
+ name = params['disk_name'],
|
|
|
97ae69 |
+ description = "Uploaded by virt-v2v",
|
|
|
97ae69 |
+ format = disk_format,
|
|
|
97ae69 |
+ initial_size = params['disk_size'],
|
|
|
97ae69 |
+ provisioned_size = params['disk_size'],
|
|
|
97ae69 |
+ # XXX Ignores params['output_sparse'].
|
|
|
97ae69 |
+ # Handling this properly will be complex, see:
|
|
|
97ae69 |
+ # https://www.redhat.com/archives/libguestfs/2018-March/msg00177.html
|
|
|
97ae69 |
+ sparse = True,
|
|
|
97ae69 |
+ storage_domains = [
|
|
|
97ae69 |
+ types.StorageDomain(
|
|
|
97ae69 |
+ name = params['output_storage'],
|
|
|
97ae69 |
+ )
|
|
|
97ae69 |
+ ],
|
|
|
97ae69 |
+ )
|
|
|
97ae69 |
+ )
|
|
|
97ae69 |
+
|
|
|
97ae69 |
+ # Wait till the disk is up, as the transfer can't start if the
|
|
|
97ae69 |
+ # disk is locked:
|
|
|
97ae69 |
+ disk_service = disks_service.disk_service(disk.id)
|
|
|
97ae69 |
+ debug("disk.id = %r" % disk.id)
|
|
|
97ae69 |
+
|
|
|
97ae69 |
+ endt = time.time() + timeout
|
|
|
97ae69 |
+ while True:
|
|
|
97ae69 |
+ time.sleep(5)
|
|
|
97ae69 |
+ disk = disk_service.get()
|
|
|
97ae69 |
+ if disk.status == types.DiskStatus.OK:
|
|
|
97ae69 |
+ break
|
|
|
97ae69 |
+ if time.time() > endt:
|
|
|
97ae69 |
+ raise RuntimeError("timed out waiting for disk to become unlocked")
|
|
|
97ae69 |
+
|
|
|
97ae69 |
+ # Get a reference to the transfer service.
|
|
|
97ae69 |
+ transfers_service = system_service.image_transfers_service()
|
|
|
97ae69 |
+
|
|
|
97ae69 |
+ # Create a new image transfer.
|
|
|
97ae69 |
+ transfer = transfers_service.add(
|
|
|
97ae69 |
+ types.ImageTransfer(
|
|
|
97ae69 |
+ image = types.Image(
|
|
|
97ae69 |
+ id = disk.id
|
|
|
97ae69 |
+ )
|
|
|
97ae69 |
+ )
|
|
|
97ae69 |
+ )
|
|
|
97ae69 |
+ debug("transfer.id = %r" % transfer.id)
|
|
|
97ae69 |
+
|
|
|
97ae69 |
+ # Get a reference to the created transfer service.
|
|
|
97ae69 |
+ transfer_service = transfers_service.image_transfer_service(transfer.id)
|
|
|
97ae69 |
+
|
|
|
97ae69 |
+ # After adding a new transfer for the disk, the transfer's status
|
|
|
97ae69 |
+ # will be INITIALIZING. Wait until the init phase is over. The
|
|
|
97ae69 |
+ # actual transfer can start when its status is "Transferring".
|
|
|
97ae69 |
+ endt = time.time() + timeout
|
|
|
97ae69 |
+ while True:
|
|
|
97ae69 |
+ time.sleep(5)
|
|
|
97ae69 |
+ transfer = transfer_service.get()
|
|
|
97ae69 |
+ if transfer.phase != types.ImageTransferPhase.INITIALIZING:
|
|
|
97ae69 |
+ break
|
|
|
97ae69 |
+ if time.time() > endt:
|
|
|
97ae69 |
+ raise RuntimeError("timed out waiting for transfer status " +
|
|
|
97ae69 |
+ "!= INITIALIZING")
|
|
|
97ae69 |
+
|
|
|
97ae69 |
+ # Now we have permission to start the transfer.
|
|
|
97ae69 |
+ if params['rhv_direct']:
|
|
|
97ae69 |
+ if transfer.transfer_url is None:
|
|
|
97ae69 |
+ raise RuntimeError("direct upload to host not supported, " +
|
|
|
97ae69 |
+ "requires ovirt-engine >= 4.2 and only works " +
|
|
|
97ae69 |
+ "when virt-v2v is run within the oVirt/RHV " +
|
|
|
97ae69 |
+ "environment, eg. on an oVirt node.")
|
|
|
97ae69 |
+ destination_url = urlparse(transfer.transfer_url)
|
|
|
97ae69 |
+ else:
|
|
|
97ae69 |
+ destination_url = urlparse(transfer.proxy_url)
|
|
|
97ae69 |
+
|
|
|
97ae69 |
+ context = ssl.create_default_context()
|
|
|
97ae69 |
+ context.load_verify_locations(cafile = params['rhv_cafile'])
|
|
|
97ae69 |
+
|
|
|
97ae69 |
+ http = HTTPSConnection(
|
|
|
97ae69 |
+ destination_url.hostname,
|
|
|
97ae69 |
+ destination_url.port,
|
|
|
97ae69 |
+ context = context
|
|
|
97ae69 |
+ )
|
|
|
97ae69 |
+
|
|
|
97ae69 |
+ # Save everything we need to make requests in the handle.
|
|
|
97ae69 |
+ return {
|
|
|
97ae69 |
+ 'can_flush': False,
|
|
|
97ae69 |
+ 'can_trim': False,
|
|
|
97ae69 |
+ 'can_zero': False,
|
|
|
97ae69 |
+ 'connection': connection,
|
|
|
97ae69 |
+ 'disk': disk,
|
|
|
97ae69 |
+ 'disk_service': disk_service,
|
|
|
97ae69 |
+ 'failed': False,
|
|
|
97ae69 |
+ 'got_options': False,
|
|
|
97ae69 |
+ 'highestwrite': 0,
|
|
|
97ae69 |
+ 'http': http,
|
|
|
97ae69 |
+ 'needs_auth': not params['rhv_direct'],
|
|
|
97ae69 |
+ 'path': destination_url.path,
|
|
|
97ae69 |
+ 'transfer': transfer,
|
|
|
97ae69 |
+ 'transfer_service': transfer_service,
|
|
|
97ae69 |
+ }
|
|
|
97ae69 |
+
|
|
|
97ae69 |
+# Can we issue zero, trim or flush requests?
|
|
|
97ae69 |
+def get_options(h):
|
|
|
97ae69 |
+ if h['got_options']:
|
|
|
97ae69 |
+ return
|
|
|
97ae69 |
+ h['got_options'] = True
|
|
|
97ae69 |
+
|
|
|
97ae69 |
+ http = h['http']
|
|
|
97ae69 |
+ transfer = h['transfer']
|
|
|
97ae69 |
+
|
|
|
97ae69 |
+ http.putrequest("OPTIONS", h['path'])
|
|
|
97ae69 |
+ http.putheader("Authorization", transfer.signed_ticket)
|
|
|
97ae69 |
+ http.endheaders()
|
|
|
97ae69 |
+
|
|
|
97ae69 |
+ r = http.getresponse()
|
|
|
97ae69 |
+ if r.status == 200:
|
|
|
97ae69 |
+ # New imageio never needs authentication.
|
|
|
97ae69 |
+ h['needs_auth'] = False
|
|
|
97ae69 |
+
|
|
|
97ae69 |
+ j = json.loads(r.read())
|
|
|
97ae69 |
+ h['can_zero'] = "zero" in j['features']
|
|
|
97ae69 |
+ h['can_trim'] = "trim" in j['features']
|
|
|
97ae69 |
+ h['can_flush'] = "flush" in j['features']
|
|
|
97ae69 |
+
|
|
|
97ae69 |
+ # Old imageio servers returned either 405 Method Not Allowed or
|
|
|
97ae69 |
+ # 204 No Content (with an empty body). If we see that we leave
|
|
|
97ae69 |
+ # all the features as False and they will be emulated.
|
|
|
97ae69 |
+ elif r.status == 405 or r.status == 204:
|
|
|
97ae69 |
+ pass
|
|
|
97ae69 |
+
|
|
|
97ae69 |
+ else:
|
|
|
97ae69 |
+ raise RuntimeError("could not use OPTIONS request: %d: %s" %
|
|
|
97ae69 |
+ (r.status, r.reason))
|
|
|
97ae69 |
+
|
|
|
97ae69 |
+def can_trim(h):
|
|
|
97ae69 |
+ get_options(h)
|
|
|
97ae69 |
+ return h['can_trim']
|
|
|
97ae69 |
+
|
|
|
97ae69 |
+def can_flush(h):
|
|
|
97ae69 |
+ get_options(h)
|
|
|
97ae69 |
+ return h['can_flush']
|
|
|
97ae69 |
+
|
|
|
97ae69 |
+def get_size(h):
|
|
|
97ae69 |
+ return params['disk_size']
|
|
|
97ae69 |
+
|
|
|
97ae69 |
+# For documentation see:
|
|
|
97ae69 |
+# https://github.com/oVirt/ovirt-imageio/blob/master/docs/random-io.md
|
|
|
97ae69 |
+# For examples of working code to read/write from the server, see:
|
|
|
97ae69 |
+# https://github.com/oVirt/ovirt-imageio/blob/master/daemon/test/server_test.py
|
|
|
97ae69 |
+
|
|
|
97ae69 |
+def pread(h, count, offset):
|
|
|
97ae69 |
+ http = h['http']
|
|
|
97ae69 |
+ transfer = h['transfer']
|
|
|
97ae69 |
+ transfer_service = h['transfer_service']
|
|
|
97ae69 |
+
|
|
|
97ae69 |
+ http.putrequest("GET", h['path'])
|
|
|
97ae69 |
+ # Authorization is only needed for old imageio.
|
|
|
97ae69 |
+ if h['needs_auth']:
|
|
|
97ae69 |
+ http.putheader("Authorization", transfer.signed_ticket)
|
|
|
97ae69 |
+ http.putheader("Range", "bytes=%d-%d" % (offset, offset+count-1))
|
|
|
97ae69 |
+ http.endheaders()
|
|
|
97ae69 |
+
|
|
|
97ae69 |
+ r = http.getresponse()
|
|
|
97ae69 |
+ # 206 = HTTP Partial Content.
|
|
|
97ae69 |
+ if r.status != 206:
|
|
|
97ae69 |
+ h['transfer_service'].pause()
|
|
|
97ae69 |
+ h['failed'] = True
|
|
|
97ae69 |
+ raise RuntimeError("could not read sector (%d, %d): %d: %s" %
|
|
|
97ae69 |
+ (offset, count, r.status, r.reason))
|
|
|
97ae69 |
+ return r.read()
|
|
|
97ae69 |
+
|
|
|
97ae69 |
+def pwrite(h, buf, offset):
|
|
|
97ae69 |
+ http = h['http']
|
|
|
97ae69 |
+ transfer = h['transfer']
|
|
|
97ae69 |
+ transfer_service = h['transfer_service']
|
|
|
97ae69 |
+
|
|
|
97ae69 |
+ count = len(buf)
|
|
|
97ae69 |
+ h['highestwrite'] = max(h['highestwrite'], offset+count)
|
|
|
97ae69 |
+
|
|
|
97ae69 |
+ http.putrequest("PUT", h['path'] + "?flush=n")
|
|
|
97ae69 |
+ # Authorization is only needed for old imageio.
|
|
|
97ae69 |
+ if h['needs_auth']:
|
|
|
97ae69 |
+ http.putheader("Authorization", transfer.signed_ticket)
|
|
|
97ae69 |
+ # The oVirt server only uses the first part of the range, and the
|
|
|
97ae69 |
+ # content-length.
|
|
|
97ae69 |
+ http.putheader("Content-Range", "bytes %d-%d/*" % (offset, offset+count-1))
|
|
|
97ae69 |
+ http.putheader("Content-Length", str(count))
|
|
|
97ae69 |
+ http.endheaders()
|
|
|
97ae69 |
+ http.send(buf)
|
|
|
97ae69 |
+
|
|
|
97ae69 |
+ r = http.getresponse()
|
|
|
97ae69 |
+ if r.status != 200:
|
|
|
97ae69 |
+ transfer_service.pause()
|
|
|
97ae69 |
+ h['failed'] = True
|
|
|
97ae69 |
+ raise RuntimeError("could not write sector (%d, %d): %d: %s" %
|
|
|
97ae69 |
+ (offset, count, r.status, r.reason))
|
|
|
97ae69 |
+
|
|
|
97ae69 |
+def zero(h, count, offset, may_trim):
|
|
|
97ae69 |
+ http = h['http']
|
|
|
97ae69 |
+ transfer = h['transfer']
|
|
|
97ae69 |
+ transfer_service = h['transfer_service']
|
|
|
97ae69 |
+
|
|
|
97ae69 |
+ # Unlike the trim and flush calls, there is no 'can_zero' method
|
|
|
97ae69 |
+ # so nbdkit could call this even if the server doesn't support
|
|
|
97ae69 |
+ # zeroing. If this is the case we must emulate.
|
|
|
97ae69 |
+ if not h['can_zero']:
|
|
|
97ae69 |
+ emulate_zero(h, count, offset)
|
|
|
97ae69 |
+ return
|
|
|
97ae69 |
+
|
|
|
97ae69 |
+ # Construct the JSON request for zeroing.
|
|
|
97ae69 |
+ buf = json.dumps({'op': "zero",
|
|
|
97ae69 |
+ 'offset': offset,
|
|
|
97ae69 |
+ 'size': count,
|
|
|
97ae69 |
+ 'flush': False}).encode()
|
|
|
97ae69 |
+
|
|
|
97ae69 |
+ http.putrequest("PATCH", h['path'])
|
|
|
97ae69 |
+ http.putheader("Content-Type", "application/json")
|
|
|
97ae69 |
+ http.putheader("Content-Length", len(buf))
|
|
|
97ae69 |
+ http.endheaders()
|
|
|
97ae69 |
+ http.send(buf)
|
|
|
97ae69 |
+
|
|
|
97ae69 |
+ r = http.getresponse()
|
|
|
97ae69 |
+ if r.status != 200:
|
|
|
97ae69 |
+ transfer_service.pause()
|
|
|
97ae69 |
+ h['failed'] = True
|
|
|
97ae69 |
+ raise RuntimeError("could not zero sector (%d, %d): %d: %s" %
|
|
|
97ae69 |
+ (offset, count, r.status, r.reason))
|
|
|
97ae69 |
+
|
|
|
97ae69 |
+def emulate_zero(h, count, offset):
|
|
|
97ae69 |
+ # qemu-img convert starts by trying to zero/trim the whole device.
|
|
|
97ae69 |
+ # Since we've just created a new disk it's safe to ignore these
|
|
|
97ae69 |
+ # requests as long as they are smaller than the highest write seen.
|
|
|
97ae69 |
+ # After that we must emulate them with writes.
|
|
|
97ae69 |
+ if offset+count < h['highestwrite']:
|
|
|
97ae69 |
+ http.putrequest("PUT", h['path'])
|
|
|
97ae69 |
+ # Authorization is only needed for old imageio.
|
|
|
97ae69 |
+ if h['needs_auth']:
|
|
|
97ae69 |
+ http.putheader("Authorization", transfer.signed_ticket)
|
|
|
97ae69 |
+ http.putheader("Content-Range",
|
|
|
97ae69 |
+ "bytes %d-%d/*" % (offset, offset+count-1))
|
|
|
97ae69 |
+ http.putheader("Content-Length", str(count))
|
|
|
97ae69 |
+ http.endheaders()
|
|
|
97ae69 |
+
|
|
|
97ae69 |
+ buf = bytearray(128*1024)
|
|
|
97ae69 |
+ while count > len(buf):
|
|
|
97ae69 |
+ http.send(buf)
|
|
|
97ae69 |
+ count -= len(buf)
|
|
|
97ae69 |
+ http.send(buffer(buf, 0, count))
|
|
|
97ae69 |
+
|
|
|
97ae69 |
+ r = http.getresponse()
|
|
|
97ae69 |
+ if r.status != 200:
|
|
|
97ae69 |
+ transfer_service.pause()
|
|
|
97ae69 |
+ h['failed'] = True
|
|
|
97ae69 |
+ raise RuntimeError("could not write zeroes (%d, %d): %d: %s" %
|
|
|
97ae69 |
+ (offset, count, r.status, r.reason))
|
|
|
97ae69 |
+
|
|
|
97ae69 |
+def trim(h, count, offset):
|
|
|
97ae69 |
+ http = h['http']
|
|
|
97ae69 |
+ transfer = h['transfer']
|
|
|
97ae69 |
+ transfer_service = h['transfer_service']
|
|
|
97ae69 |
+
|
|
|
97ae69 |
+ # Construct the JSON request for trimming.
|
|
|
97ae69 |
+ buf = json.dumps({'op': "trim",
|
|
|
97ae69 |
+ 'offset': offset,
|
|
|
97ae69 |
+ 'size': count,
|
|
|
97ae69 |
+ 'flush': False}).encode()
|
|
|
97ae69 |
+
|
|
|
97ae69 |
+ http.putrequest("PATCH", h['path'])
|
|
|
97ae69 |
+ http.putheader("Content-Type", "application/json")
|
|
|
97ae69 |
+ http.putheader("Content-Length", len(buf))
|
|
|
97ae69 |
+ http.endheaders()
|
|
|
97ae69 |
+ http.send(buf)
|
|
|
97ae69 |
+
|
|
|
97ae69 |
+ r = http.getresponse()
|
|
|
97ae69 |
+ if r.status != 200:
|
|
|
97ae69 |
+ transfer_service.pause()
|
|
|
97ae69 |
+ h['failed'] = True
|
|
|
97ae69 |
+ raise RuntimeError("could not trim sector (%d, %d): %d: %s" %
|
|
|
97ae69 |
+ (offset, count, r.status, r.reason))
|
|
|
97ae69 |
+
|
|
|
97ae69 |
+def flush(h):
|
|
|
97ae69 |
+ http = h['http']
|
|
|
97ae69 |
+ transfer = h['transfer']
|
|
|
97ae69 |
+ transfer_service = h['transfer_service']
|
|
|
97ae69 |
+
|
|
|
97ae69 |
+ # Construct the JSON request for flushing.
|
|
|
97ae69 |
+ buf = json.dumps({'op': "flush"}).encode()
|
|
|
97ae69 |
+
|
|
|
97ae69 |
+ http.putrequest("PATCH", h['path'])
|
|
|
97ae69 |
+ http.putheader("Content-Type", "application/json")
|
|
|
97ae69 |
+ http.putheader("Content-Length", len(buf))
|
|
|
97ae69 |
+ http.endheaders()
|
|
|
97ae69 |
+ http.send(buf)
|
|
|
97ae69 |
+
|
|
|
97ae69 |
+ r = http.getresponse()
|
|
|
97ae69 |
+ if r.status != 200:
|
|
|
97ae69 |
+ transfer_service.pause()
|
|
|
97ae69 |
+ h['failed'] = True
|
|
|
97ae69 |
+ raise RuntimeError("could not flush: %d: %s" % (r.status, r.reason))
|
|
|
97ae69 |
+
|
|
|
97ae69 |
+def delete_disk_on_failure(h):
|
|
|
97ae69 |
+ disk_service = h['disk_service']
|
|
|
97ae69 |
+ disk_service.remove()
|
|
|
97ae69 |
+
|
|
|
97ae69 |
+def close(h):
|
|
|
97ae69 |
+ http = h['http']
|
|
|
97ae69 |
+ connection = h['connection']
|
|
|
97ae69 |
+
|
|
|
97ae69 |
+ # This is sometimes necessary because python doesn't set up
|
|
|
97ae69 |
+ # sys.stderr to be line buffered and so debug, errors or
|
|
|
97ae69 |
+ # exceptions printed previously might not be emitted before the
|
|
|
97ae69 |
+ # plugin exits.
|
|
|
97ae69 |
+ sys.stderr.flush()
|
|
|
97ae69 |
+
|
|
|
97ae69 |
+ # If the connection failed earlier ensure we clean up the disk.
|
|
|
97ae69 |
+ if h['failed']:
|
|
|
97ae69 |
+ delete_disk_on_failure(h)
|
|
|
97ae69 |
+ connection.close()
|
|
|
97ae69 |
+ return
|
|
|
97ae69 |
+
|
|
|
97ae69 |
+ try:
|
|
|
97ae69 |
+ # Issue a flush request on close so that the data is written to
|
|
|
97ae69 |
+ # persistent store before we create the VM.
|
|
|
97ae69 |
+ if h['can_flush']:
|
|
|
97ae69 |
+ flush(h)
|
|
|
97ae69 |
+
|
|
|
97ae69 |
+ http.close()
|
|
|
97ae69 |
+
|
|
|
97ae69 |
+ disk = h['disk']
|
|
|
97ae69 |
+ transfer_service = h['transfer_service']
|
|
|
97ae69 |
+
|
|
|
97ae69 |
+ transfer_service.finalize()
|
|
|
97ae69 |
+
|
|
|
97ae69 |
+ # Wait until the transfer disk job is completed since
|
|
|
97ae69 |
+ # only then we can be sure the disk is unlocked. As this
|
|
|
97ae69 |
+ # code is not very clear, what's happening is that we are
|
|
|
97ae69 |
+ # waiting for the transfer object to cease to exist, which
|
|
|
97ae69 |
+ # falls through to the exception case and then we can
|
|
|
97ae69 |
+ # continue.
|
|
|
97ae69 |
+ endt = time.time() + timeout
|
|
|
97ae69 |
+ try:
|
|
|
97ae69 |
+ while True:
|
|
|
97ae69 |
+ time.sleep(1)
|
|
|
97ae69 |
+ tmp = transfer_service.get()
|
|
|
97ae69 |
+ if time.time() > endt:
|
|
|
97ae69 |
+ raise RuntimeError("timed out waiting for transfer " +
|
|
|
97ae69 |
+ "to finalize")
|
|
|
97ae69 |
+ except sdk.NotFoundError:
|
|
|
97ae69 |
+ pass
|
|
|
97ae69 |
+
|
|
|
97ae69 |
+ # Write the disk ID file. Only do this on successful completion.
|
|
|
97ae69 |
+ with builtins.open(params['diskid_file'], 'w') as fp:
|
|
|
97ae69 |
+ fp.write(disk.id)
|
|
|
97ae69 |
+
|
|
|
97ae69 |
+ except:
|
|
|
97ae69 |
+ # Otherwise on any failure we must clean up the disk.
|
|
|
97ae69 |
+ delete_disk_on_failure(h)
|
|
|
97ae69 |
+ raise
|
|
|
97ae69 |
+
|
|
|
97ae69 |
+ connection.close()
|
|
|
97ae69 |
diff --git a/v2v/rhv-upload-precheck.py b/v2v/rhv-upload-precheck.py
|
|
|
97ae69 |
new file mode 100644
|
|
|
97ae69 |
index 000000000..2798a29dd
|
|
|
97ae69 |
--- /dev/null
|
|
|
97ae69 |
+++ b/v2v/rhv-upload-precheck.py
|
|
|
97ae69 |
@@ -0,0 +1,73 @@
|
|
|
97ae69 |
+# -*- python -*-
|
|
|
97ae69 |
+# oVirt or RHV pre-upload checks used by ‘virt-v2v -o rhv-upload’
|
|
|
97ae69 |
+# Copyright (C) 2018 Red Hat Inc.
|
|
|
97ae69 |
+#
|
|
|
97ae69 |
+# This program is free software; you can redistribute it and/or modify
|
|
|
97ae69 |
+# it under the terms of the GNU General Public License as published by
|
|
|
97ae69 |
+# the Free Software Foundation; either version 2 of the License, or
|
|
|
97ae69 |
+# (at your option) any later version.
|
|
|
97ae69 |
+#
|
|
|
97ae69 |
+# This program is distributed in the hope that it will be useful,
|
|
|
97ae69 |
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
97ae69 |
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
97ae69 |
+# GNU General Public License for more details.
|
|
|
97ae69 |
+#
|
|
|
97ae69 |
+# You should have received a copy of the GNU General Public License along
|
|
|
97ae69 |
+# with this program; if not, write to the Free Software Foundation, Inc.,
|
|
|
97ae69 |
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
|
|
97ae69 |
+
|
|
|
97ae69 |
+import json
|
|
|
97ae69 |
+import logging
|
|
|
97ae69 |
+import sys
|
|
|
97ae69 |
+import time
|
|
|
97ae69 |
+
|
|
|
97ae69 |
+from http.client import HTTPSConnection
|
|
|
97ae69 |
+from urllib.parse import urlparse
|
|
|
97ae69 |
+
|
|
|
97ae69 |
+import ovirtsdk4 as sdk
|
|
|
97ae69 |
+import ovirtsdk4.types as types
|
|
|
97ae69 |
+
|
|
|
97ae69 |
+# Parameters are passed in via a JSON doc from the OCaml code.
|
|
|
97ae69 |
+# Because this Python code ships embedded inside virt-v2v there
|
|
|
97ae69 |
+# is no formal API here.
|
|
|
97ae69 |
+params = None
|
|
|
97ae69 |
+
|
|
|
97ae69 |
+if len(sys.argv) != 2:
|
|
|
97ae69 |
+ raise RuntimeError("incorrect number of parameters")
|
|
|
97ae69 |
+
|
|
|
97ae69 |
+# Parameters are passed in via a JSON document.
|
|
|
97ae69 |
+with open(sys.argv[1], 'r') as fp:
|
|
|
97ae69 |
+ params = json.load(fp)
|
|
|
97ae69 |
+
|
|
|
97ae69 |
+# What is passed in is a password file, read the actual password.
|
|
|
97ae69 |
+with open(params['output_password'], 'r') as fp:
|
|
|
97ae69 |
+ output_password = fp.read()
|
|
|
97ae69 |
+output_password = output_password.rstrip()
|
|
|
97ae69 |
+
|
|
|
97ae69 |
+# Parse out the username from the output_conn URL.
|
|
|
97ae69 |
+parsed = urlparse(params['output_conn'])
|
|
|
97ae69 |
+username = parsed.username or "admin@internal"
|
|
|
97ae69 |
+
|
|
|
97ae69 |
+# Connect to the server.
|
|
|
97ae69 |
+connection = sdk.Connection(
|
|
|
97ae69 |
+ url = params['output_conn'],
|
|
|
97ae69 |
+ username = username,
|
|
|
97ae69 |
+ password = output_password,
|
|
|
97ae69 |
+ ca_file = params['rhv_cafile'],
|
|
|
97ae69 |
+ log = logging.getLogger(),
|
|
|
97ae69 |
+ insecure = params['insecure'],
|
|
|
97ae69 |
+)
|
|
|
97ae69 |
+
|
|
|
97ae69 |
+system_service = connection.system_service()
|
|
|
97ae69 |
+
|
|
|
97ae69 |
+# Find if a virtual machine already exists with that name.
|
|
|
97ae69 |
+vms_service = system_service.vms_service()
|
|
|
97ae69 |
+vms = vms_service.list(
|
|
|
97ae69 |
+ search = ("name=%s" % params['output_name']),
|
|
|
97ae69 |
+)
|
|
|
97ae69 |
+if len(vms) > 0:
|
|
|
97ae69 |
+ vm = vms[0]
|
|
|
97ae69 |
+ raise RuntimeError("VM already exists with name ‘%s’, id ‘%s’" %
|
|
|
97ae69 |
+ (params['output_name'], vm.id))
|
|
|
97ae69 |
+
|
|
|
97ae69 |
+# Otherwise everything is OK, exit with no error.
|
|
|
97ae69 |
diff --git a/v2v/test-v2v-o-rhv-upload-oo-query.sh b/v2v/test-v2v-o-rhv-upload-oo-query.sh
|
|
|
97ae69 |
new file mode 100755
|
|
|
97ae69 |
index 000000000..29d69e1c1
|
|
|
97ae69 |
--- /dev/null
|
|
|
97ae69 |
+++ b/v2v/test-v2v-o-rhv-upload-oo-query.sh
|
|
|
97ae69 |
@@ -0,0 +1,38 @@
|
|
|
97ae69 |
+#!/bin/bash -
|
|
|
97ae69 |
+# libguestfs virt-v2v test script
|
|
|
97ae69 |
+# Copyright (C) 2018 Red Hat Inc.
|
|
|
97ae69 |
+#
|
|
|
97ae69 |
+# This program is free software; you can redistribute it and/or modify
|
|
|
97ae69 |
+# it under the terms of the GNU General Public License as published by
|
|
|
97ae69 |
+# the Free Software Foundation; either version 2 of the License, or
|
|
|
97ae69 |
+# (at your option) any later version.
|
|
|
97ae69 |
+#
|
|
|
97ae69 |
+# This program is distributed in the hope that it will be useful,
|
|
|
97ae69 |
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
97ae69 |
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
97ae69 |
+# GNU General Public License for more details.
|
|
|
97ae69 |
+#
|
|
|
97ae69 |
+# You should have received a copy of the GNU General Public License
|
|
|
97ae69 |
+# along with this program; if not, write to the Free Software
|
|
|
97ae69 |
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
|
|
97ae69 |
+
|
|
|
97ae69 |
+# Test -oo "?" option.
|
|
|
97ae69 |
+
|
|
|
97ae69 |
+set -e
|
|
|
97ae69 |
+
|
|
|
97ae69 |
+$TEST_FUNCTIONS
|
|
|
97ae69 |
+skip_if_skipped
|
|
|
97ae69 |
+
|
|
|
97ae69 |
+export VIRT_TOOLS_DATA_DIR="$top_srcdir/test-data/fake-virt-tools"
|
|
|
97ae69 |
+export VIRTIO_WIN="$top_srcdir/test-data/fake-virtio-win"
|
|
|
97ae69 |
+
|
|
|
97ae69 |
+f=test-v2v-o-rhv-upload-oo-query.actual
|
|
|
97ae69 |
+rm -f $f
|
|
|
97ae69 |
+
|
|
|
97ae69 |
+$VG virt-v2v --debug-gc \
|
|
|
97ae69 |
+ -o rhv-upload -oo "?" > $f
|
|
|
97ae69 |
+
|
|
|
97ae69 |
+grep -- "-oo rhv-cafile" $f
|
|
|
97ae69 |
+grep -- "-oo rhv-verifypeer" $f
|
|
|
97ae69 |
+
|
|
|
97ae69 |
+rm $f
|
|
|
97ae69 |
diff --git a/v2v/test-v2v-python-syntax.sh b/v2v/test-v2v-python-syntax.sh
|
|
|
97ae69 |
new file mode 100755
|
|
|
97ae69 |
index 000000000..b167f4610
|
|
|
97ae69 |
--- /dev/null
|
|
|
97ae69 |
+++ b/v2v/test-v2v-python-syntax.sh
|
|
|
97ae69 |
@@ -0,0 +1,45 @@
|
|
|
97ae69 |
+#!/bin/bash -
|
|
|
97ae69 |
+# libguestfs
|
|
|
97ae69 |
+# Copyright (C) 2018 Red Hat Inc.
|
|
|
97ae69 |
+#
|
|
|
97ae69 |
+# This program is free software; you can redistribute it and/or modify
|
|
|
97ae69 |
+# it under the terms of the GNU General Public License as published by
|
|
|
97ae69 |
+# the Free Software Foundation; either version 2 of the License, or
|
|
|
97ae69 |
+# (at your option) any later version.
|
|
|
97ae69 |
+#
|
|
|
97ae69 |
+# This program is distributed in the hope that it will be useful,
|
|
|
97ae69 |
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
97ae69 |
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
97ae69 |
+# GNU General Public License for more details.
|
|
|
97ae69 |
+#
|
|
|
97ae69 |
+# You should have received a copy of the GNU General Public License
|
|
|
97ae69 |
+# along with this program; if not, write to the Free Software
|
|
|
97ae69 |
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
|
|
97ae69 |
+
|
|
|
97ae69 |
+set -e
|
|
|
97ae69 |
+
|
|
|
97ae69 |
+$TEST_FUNCTIONS
|
|
|
97ae69 |
+skip_if_skipped
|
|
|
97ae69 |
+
|
|
|
97ae69 |
+# Files to check.
|
|
|
97ae69 |
+files="rhv-upload-createvm.py rhv-upload-plugin.py rhv-upload-precheck.py"
|
|
|
97ae69 |
+
|
|
|
97ae69 |
+# Base version of Python.
|
|
|
97ae69 |
+python=python3
|
|
|
97ae69 |
+
|
|
|
97ae69 |
+# Checks the files are syntactically correct, but not very much else.
|
|
|
97ae69 |
+for f in $files; do
|
|
|
97ae69 |
+ $python -m py_compile $f
|
|
|
97ae69 |
+done
|
|
|
97ae69 |
+
|
|
|
97ae69 |
+# Checks the files correspond to PEP8 coding style.
|
|
|
97ae69 |
+# https://www.python.org/dev/peps/pep-0008/
|
|
|
97ae69 |
+if $python-pep8 --version >/dev/null 2>&1; then
|
|
|
97ae69 |
+ for f in $files; do
|
|
|
97ae69 |
+ # Ignore:
|
|
|
97ae69 |
+ # E226 missing whitespace around arithmetic operator
|
|
|
97ae69 |
+ # E251 unexpected spaces around keyword / parameter equals
|
|
|
97ae69 |
+ # E302 expected 2 blank lines, found 1
|
|
|
97ae69 |
+ $python-pep8 --ignore=E226,E251,E302 $f
|
|
|
97ae69 |
+ done
|
|
|
97ae69 |
+fi
|
|
|
97ae69 |
diff --git a/v2v/virt-v2v.pod b/v2v/virt-v2v.pod
|
|
|
97ae69 |
index 5dd888e77..62b726b8f 100644
|
|
|
97ae69 |
--- a/v2v/virt-v2v.pod
|
|
|
97ae69 |
+++ b/v2v/virt-v2v.pod
|
|
|
97ae69 |
@@ -6,15 +6,18 @@ virt-v2v - Convert a guest to use KVM
|
|
|
97ae69 |
|
|
|
97ae69 |
virt-v2v -ic vpx://vcenter.example.com/Datacenter/esxi vmware_guest
|
|
|
97ae69 |
|
|
|
97ae69 |
- virt-v2v -ic vpx://vcenter.example.com/Datacenter/esxi vmware_guest \
|
|
|
97ae69 |
- -o rhv -os rhv.nfs:/export_domain --bridge ovirtmgmt
|
|
|
97ae69 |
-
|
|
|
97ae69 |
virt-v2v -i libvirtxml guest-domain.xml -o local -os /var/tmp
|
|
|
97ae69 |
|
|
|
97ae69 |
virt-v2v -i disk disk.img -o local -os /var/tmp
|
|
|
97ae69 |
|
|
|
97ae69 |
virt-v2v -i disk disk.img -o glance
|
|
|
97ae69 |
|
|
|
97ae69 |
+ virt-v2v -ic vpx://vcenter.example.com/Datacenter/esxi vmware_guest \
|
|
|
97ae69 |
+ -o rhv-upload -oc https://ovirt-engine.example.com/ovirt-engine/api \
|
|
|
97ae69 |
+ -os ovirt-data -op /tmp/ovirt-admin-password -of raw \
|
|
|
97ae69 |
+ -oo rhv-cafile=/tmp/ca.pem -oo rhv-direct \
|
|
|
97ae69 |
+ --bridge ovirtmgmt
|
|
|
97ae69 |
+
|
|
|
97ae69 |
=head1 DESCRIPTION
|
|
|
97ae69 |
|
|
|
97ae69 |
Virt-v2v converts guests from a foreign hypervisor to run on KVM. It
|
|
|
97ae69 |
@@ -50,20 +53,18 @@ For more information see L</INPUT FROM VMWARE VCENTER SERVER> below.
|
|
|
97ae69 |
=head2 Convert from VMware to RHV/oVirt
|
|
|
97ae69 |
|
|
|
97ae69 |
This is the same as the previous example, except you want to send the
|
|
|
97ae69 |
-guest to a RHV-M Export Storage Domain which is located remotely
|
|
|
97ae69 |
-(over NFS) at C<rhv.nfs:/export_domain>. If you are unclear about
|
|
|
97ae69 |
-the location of the Export Storage Domain you should check the
|
|
|
97ae69 |
-settings on your RHV-M management console. Guest network
|
|
|
97ae69 |
+guest to a RHV Data Domain using the RHV REST API. Guest network
|
|
|
97ae69 |
interface(s) are connected to the target network called C<ovirtmgmt>.
|
|
|
97ae69 |
|
|
|
97ae69 |
virt-v2v -ic vpx://vcenter.example.com/Datacenter/esxi vmware_guest \
|
|
|
97ae69 |
- -o rhv -os rhv.nfs:/export_domain --bridge ovirtmgmt
|
|
|
97ae69 |
+ -o rhv-upload -oc https://ovirt-engine.example.com/ovirt-engine/api \
|
|
|
97ae69 |
+ -os ovirt-data -op /tmp/ovirt-admin-password -of raw \
|
|
|
97ae69 |
+ -oo rhv-cafile=/tmp/ca.pem -oo rhv-direct \
|
|
|
97ae69 |
+ --bridge ovirtmgmt
|
|
|
97ae69 |
|
|
|
97ae69 |
In this case the host running virt-v2v acts as a B<conversion server>.
|
|
|
97ae69 |
|
|
|
97ae69 |
-Note that after conversion, the guest will appear in the RHV-M Export
|
|
|
97ae69 |
-Storage Domain, from where you will need to import it using the RHV-M
|
|
|
97ae69 |
-user interface. (See L</OUTPUT TO RHV>).
|
|
|
97ae69 |
+For more information see L</OUTPUT TO RHV> below.
|
|
|
97ae69 |
|
|
|
97ae69 |
=head2 Convert from ESXi hypervisor over SSH to local libvirt
|
|
|
97ae69 |
|
|
|
97ae69 |
@@ -122,9 +123,9 @@ command line.
|
|
|
97ae69 |
Xen ───▶│ -i libvirt ──▶ │ │ │ (default) │
|
|
|
97ae69 |
... ───▶│ (default) │ │ │ ──┐ └────────────┘
|
|
|
97ae69 |
└────────────┘ │ │ ─┐└──────▶ -o glance
|
|
|
97ae69 |
- -i libvirtxml ─────────▶ │ │ ┐└─────────▶ -o rhv
|
|
|
97ae69 |
- -i vmx ────────────────▶ │ │ └──────────▶ -o vdsm
|
|
|
97ae69 |
- └────────────┘
|
|
|
97ae69 |
+ -i libvirtxml ─────────▶ │ │ ┐├─────────▶ -o rhv
|
|
|
97ae69 |
+ -i vmx ────────────────▶ │ │ │└─────────▶ -o vdsm
|
|
|
97ae69 |
+ └────────────┘ └──────────▶ -o rhv-upload
|
|
|
97ae69 |
|
|
|
97ae69 |
Virt-v2v has a number of possible input and output modes, selected
|
|
|
97ae69 |
using the I<-i> and I<-o> options. Only one input and output mode can
|
|
|
97ae69 |
@@ -157,8 +158,9 @@ libvirt configuration file (mainly for testing).
|
|
|
97ae69 |
I<-o qemu> writes to a local disk image with a shell script for
|
|
|
97ae69 |
booting the guest directly in qemu (mainly for testing).
|
|
|
97ae69 |
|
|
|
97ae69 |
-I<-o rhv> is used to write to a RHV / oVirt target. I<-o vdsm>
|
|
|
97ae69 |
-is only used when virt-v2v runs under VDSM control.
|
|
|
97ae69 |
+I<-o rhv-upload> is used to write to a RHV / oVirt target. I<-o rhv>
|
|
|
97ae69 |
+is a legacy method to write to RHV / oVirt E<lt> 4.2. I<-o vdsm> is
|
|
|
97ae69 |
+only used when virt-v2v runs under VDSM control.
|
|
|
97ae69 |
|
|
|
97ae69 |
=head1 OPTIONS
|
|
|
97ae69 |
|
|
|
97ae69 |
@@ -426,6 +428,10 @@ written.
|
|
|
97ae69 |
|
|
|
97ae69 |
This is the same as I<-o rhv>.
|
|
|
97ae69 |
|
|
|
97ae69 |
+=item B<-o> B<ovirt-upload>
|
|
|
97ae69 |
+
|
|
|
97ae69 |
+This is the same as I<-o rhv-upload>.
|
|
|
97ae69 |
+
|
|
|
97ae69 |
=item B<-o> B<qemu>
|
|
|
97ae69 |
|
|
|
97ae69 |
Set the output method to I<qemu>.
|
|
|
97ae69 |
@@ -447,6 +453,16 @@ I<-os> parameter must also be used to specify the location of the
|
|
|
97ae69 |
Export Storage Domain. Note this does not actually import the guest
|
|
|
97ae69 |
into RHV. You have to do that manually later using the UI.
|
|
|
97ae69 |
|
|
|
97ae69 |
+See L</OUTPUT TO RHV (OLD METHOD)> below.
|
|
|
97ae69 |
+
|
|
|
97ae69 |
+=item B<-o> B<rhv-upload>
|
|
|
97ae69 |
+
|
|
|
97ae69 |
+Set the output method to I<rhv-upload>.
|
|
|
97ae69 |
+
|
|
|
97ae69 |
+The converted guest is written directly to a RHV Data Domain.
|
|
|
97ae69 |
+This is a faster method than I<-o rhv>, but requires oVirt
|
|
|
97ae69 |
+or RHV E<ge> 4.2.
|
|
|
97ae69 |
+
|
|
|
97ae69 |
See L</OUTPUT TO RHV> below.
|
|
|
97ae69 |
|
|
|
97ae69 |
=item B<-o> B<vdsm>
|
|
|
97ae69 |
@@ -488,7 +504,33 @@ the output name is the same as the input name.
|
|
|
97ae69 |
Set output option(s) related to the current output mode.
|
|
|
97ae69 |
To display short help on what options are available you can use:
|
|
|
97ae69 |
|
|
|
97ae69 |
- virt-v2v -o vdsm -oo "?"
|
|
|
97ae69 |
+ virt-v2v -o rhv-upload -oo "?"
|
|
|
97ae69 |
+
|
|
|
97ae69 |
+=item B<-oo rhv-cafile=>F<ca.pem>
|
|
|
97ae69 |
+
|
|
|
97ae69 |
+For I<-o rhv-upload> (L</OUTPUT TO RHV>) only, the F<ca.pem> file
|
|
|
97ae69 |
+(Certificate Authority), copied from F</etc/pki/ovirt-engine/ca.pem>
|
|
|
97ae69 |
+on the oVirt engine.
|
|
|
97ae69 |
+
|
|
|
97ae69 |
+=item B<-oo rhv-cluster=>C<CLUSTERNAME>
|
|
|
97ae69 |
+
|
|
|
97ae69 |
+For I<-o rhv-upload> (L</OUTPUT TO RHV>) only, set the RHV Cluster
|
|
|
97ae69 |
+Name. If not given it uses C<Default>.
|
|
|
97ae69 |
+
|
|
|
97ae69 |
+=item B<-oo rhv-direct>
|
|
|
97ae69 |
+
|
|
|
97ae69 |
+For I<-o rhv-upload> (L</OUTPUT TO RHV>) only, if this option is given
|
|
|
97ae69 |
+then virt-v2v will attempt to directly upload the disk to the oVirt
|
|
|
97ae69 |
+node, otherwise it will proxy the upload through the oVirt engine.
|
|
|
97ae69 |
+Direct upload requires that you have network access to the oVirt
|
|
|
97ae69 |
+nodes. Non-direct upload is slightly slower but should work in all
|
|
|
97ae69 |
+situations.
|
|
|
97ae69 |
+
|
|
|
97ae69 |
+=item B<-oo rhv-verifypeer>
|
|
|
97ae69 |
+
|
|
|
97ae69 |
+For I<-o rhv-upload> (L</OUTPUT TO RHV>) only, verify the oVirt/RHV
|
|
|
97ae69 |
+server’s identity by checking the server‘s certificate against the
|
|
|
97ae69 |
+Certificate Authority.
|
|
|
97ae69 |
|
|
|
97ae69 |
=item B<-oo vdsm-compat=0.10>
|
|
|
97ae69 |
|
|
|
97ae69 |
@@ -1749,6 +1791,68 @@ Define the final guest in libvirt:
|
|
|
97ae69 |
|
|
|
97ae69 |
=head1 OUTPUT TO RHV
|
|
|
97ae69 |
|
|
|
97ae69 |
+This new method to upload guests to oVirt or RHV directly via the REST
|
|
|
97ae69 |
+API requires oVirt/RHV E<ge> 4.2.
|
|
|
97ae69 |
+
|
|
|
97ae69 |
+You need to specify I<-o rhv-upload> as well as the following extra
|
|
|
97ae69 |
+parameters:
|
|
|
97ae69 |
+
|
|
|
97ae69 |
+=over 4
|
|
|
97ae69 |
+
|
|
|
97ae69 |
+=item I<-oc> C<https://ovirt-engine.example.com/ovirt-engine/api>
|
|
|
97ae69 |
+
|
|
|
97ae69 |
+The URL of the REST API which is usually the server name with
|
|
|
97ae69 |
+C</ovirt-engine/api> appended, but might be different if you installed
|
|
|
97ae69 |
+oVirt Engine on a different path.
|
|
|
97ae69 |
+
|
|
|
97ae69 |
+You can optionally add a username and port number to the URL. If the
|
|
|
97ae69 |
+username is not specified then virt-v2v defaults to using
|
|
|
97ae69 |
+C<admin@internal> which is the typical superuser account for oVirt
|
|
|
97ae69 |
+instances.
|
|
|
97ae69 |
+
|
|
|
97ae69 |
+=item I<-of raw>
|
|
|
97ae69 |
+
|
|
|
97ae69 |
+Currently you must use I<-of raw> and you cannot use I<-oa preallocated>.
|
|
|
97ae69 |
+
|
|
|
97ae69 |
+These restrictions will be loosened in a future version.
|
|
|
97ae69 |
+
|
|
|
97ae69 |
+=item I<-op> F<password-file>
|
|
|
97ae69 |
+
|
|
|
97ae69 |
+A file containing a password to be used when connecting to the oVirt
|
|
|
97ae69 |
+engine. Note the file should contain the whole password, B
|
|
|
97ae69 |
+any trailing newline>, and for security the file should have mode
|
|
|
97ae69 |
+C<0600> so that others cannot read it.
|
|
|
97ae69 |
+
|
|
|
97ae69 |
+=item I<-os> C<ovirt-data>
|
|
|
97ae69 |
+
|
|
|
97ae69 |
+The storage domain.
|
|
|
97ae69 |
+
|
|
|
97ae69 |
+=item I<-oo rhv-cafile=>F<ca.pem>
|
|
|
97ae69 |
+
|
|
|
97ae69 |
+The F<ca.pem> file (Certificate Authority), copied from
|
|
|
97ae69 |
+F</etc/pki/ovirt-engine/ca.pem> on the oVirt engine.
|
|
|
97ae69 |
+
|
|
|
97ae69 |
+=item I<-oo rhv-cluster=>C<CLUSTERNAME>
|
|
|
97ae69 |
+
|
|
|
97ae69 |
+Set the RHV Cluster Name. If not given it uses C<Default>.
|
|
|
97ae69 |
+
|
|
|
97ae69 |
+=item I<-oo rhv-direct>
|
|
|
97ae69 |
+
|
|
|
97ae69 |
+If this option is given then virt-v2v will attempt to directly upload
|
|
|
97ae69 |
+the disk to the oVirt node, otherwise it will proxy the upload through
|
|
|
97ae69 |
+the oVirt engine. Direct upload requires that you have network access
|
|
|
97ae69 |
+to the oVirt nodes. Non-direct upload is slightly slower but should
|
|
|
97ae69 |
+work in all situations.
|
|
|
97ae69 |
+
|
|
|
97ae69 |
+=item I<-oo rhv-verifypeer>
|
|
|
97ae69 |
+
|
|
|
97ae69 |
+Verify the oVirt/RHV server’s identity by checking the server‘s
|
|
|
97ae69 |
+certificate against the Certificate Authority.
|
|
|
97ae69 |
+
|
|
|
97ae69 |
+=back
|
|
|
97ae69 |
+
|
|
|
97ae69 |
+=head1 OUTPUT TO RHV (OLD METHOD)
|
|
|
97ae69 |
+
|
|
|
97ae69 |
This section only applies to the I<-o rhv> output mode. If you use
|
|
|
97ae69 |
virt-v2v from the RHV-M user interface, then behind the scenes the
|
|
|
97ae69 |
import is managed by VDSM using the I<-o vdsm> output mode (which end
|
|
|
97ae69 |
--
|
|
|
c71420 |
2.20.1
|
|
|
97ae69 |
|