Richard W.M. Jones
2022-Jul-01 11:01 UTC
[Libguestfs] [PATCH v2v 0/3] Implement -of qcow2 -oo compressed
Pre-modularised virt-v2v allows you to use either --compress or -oo compressed which basically added the -c option to qemu-img convert, compressing the qcow2 output. Since we switched over to nbdcopy this option is no longer available. The alternative is to set up the "compress" driver in qemu-nbd. However that didn't work because nbdcopy used to ignore the target minimum block size, and the compress driver requires you to obey the 64k minimum when writing, else it fails. Now that nbdcopy 1.13.5 actually fixes this problem, we can reimplement -oo compressed. This is a minimal fix for https://bugzilla.redhat.com/show_bug.cgi?id=2047660 I only implemented this for small selection of output modes that go to local files. Old virt-v2v also implemented this option (at least, in theory) for -o glance, -o rhv and -o vdsm, but all of these modes are deprecated to some degree. -o openstack which replaces glance only allows raw format uploads, and -o rhv-upload which replaces -o rhv/vdsm does its own thing completely. Rich.
Richard W.M. Jones
2022-Jul-01 11:01 UTC
[Libguestfs] [PATCH v2v 1/3] qemu-nbd: Implement output compression for qcow2 files
--- lib/qemuNBD.ml | 11 +++++++++-- lib/qemuNBD.mli | 5 +++++ output/output.ml | 38 +++++++++++++++++++++++++++++++++++--- output/output.mli | 1 + 4 files changed, 50 insertions(+), 5 deletions(-) diff --git a/lib/qemuNBD.ml b/lib/qemuNBD.ml index ae21b17c7d..bbb65f4155 100644 --- a/lib/qemuNBD.ml +++ b/lib/qemuNBD.ml @@ -55,14 +55,16 @@ type cmd = { disk : string; mutable snapshot : bool; mutable format : string option; + mutable imgopts : bool; } -let create disk = { disk; snapshot = false; format = None } +let create disk = { disk; snapshot = false; format = None; imgopts = false } let set_snapshot cmd snap = cmd.snapshot <- snap let set_format cmd format = cmd.format <- format +let set_image_opts cmd imgopts = cmd.imgopts <- imgopts -let run_unix socket { disk; snapshot; format } +let run_unix socket { disk; snapshot; format; imgopts } assert (disk <> ""); (* Create a temporary directory where we place the PID file. *) @@ -85,6 +87,11 @@ let run_unix socket { disk; snapshot; format } (* -s adds a protective overlay. *) if snapshot then List.push_back args "-s"; + (* --image-opts reinterprets the filename parameter as a set of + * image options. + *) + if imgopts then List.push_back args "--image-opts"; + if have_selinux && qemu_nbd_has_selinux_label_option () then ( List.push_back args "--selinux-label"; List.push_back args "system_u:object_r:svirt_socket_t:s0" diff --git a/lib/qemuNBD.mli b/lib/qemuNBD.mli index e10d3106c7..afe9d944e1 100644 --- a/lib/qemuNBD.mli +++ b/lib/qemuNBD.mli @@ -43,6 +43,11 @@ val set_snapshot : cmd -> bool -> unit val set_format : cmd -> string option -> unit (** Set the format [--format] parameter. *) +val set_image_opts : cmd -> bool -> unit +(** Set whether the [--image-opts] parameter is used. This changes + the meaning of the [filename] parameter to a set of image options. + Consult the qemu-nbd man page for more details. *) + val run_unix : string -> cmd -> string * int (** Start qemu-nbd command listening on a Unix domain socket, waiting for the process to start up. diff --git a/output/output.ml b/output/output.ml index 5c6670b99c..5fe8555a7a 100644 --- a/output/output.ml +++ b/output/output.ml @@ -69,7 +69,7 @@ let error_if_disk_count_gt dir n if Sys.file_exists socket then error (f_"this output module doesn't support copying more than %d disks") n -let output_to_local_file ?(changeuid = fun f -> f ()) +let output_to_local_file ?(changeuid = fun f -> f ()) ?(compressed = false) output_alloc output_format filename size socket (* Check nbdkit is installed and has the required plugin. *) if not (Nbdkit.is_installed ()) then @@ -78,6 +78,10 @@ let output_to_local_file ?(changeuid = fun f -> f ()) error (f_"nbdkit-file-plugin is not installed or not working"); let nbdkit_config = Nbdkit.config () in + (* Only allow compressed with -of qcow2. *) + if compressed && output_format <> "qcow2" then + error (f_"?-oo compressed? is only allowed when the output format is a local qcow2-format file, ie ?-of qcow2?"); + let g = open_guestfs () in let preallocation match output_alloc with @@ -103,9 +107,37 @@ let output_to_local_file ?(changeuid = fun f -> f ()) On_exit.kill pid | "qcow2" -> - let cmd = QemuNBD.create filename in + let cmd + if not compressed then ( + let cmd = QemuNBD.create filename in + QemuNBD.set_format cmd (Some "qcow2"); + cmd + ) + else ( + (* Check nbdcopy is new enough. This assumes that + * the version of libnbd is the same as the version + * of nbdcopy, but parsing this is easier. We can + * remove this check when we build-depend on + * libnbd >= 1.14. + *) + let () + let version = NBD.create () |> NBD.get_version in + let version = String.nsplit "." version in + let version = List.map int_of_string version in + if version < [1; 13; 5] then + error (f_"-oo compressed option requires nbdcopy >= 1.13.5") in + + let qemu_quote str = String.replace str "," ",," in + let image_opts = [ "driver=compress"; + "file.driver=qcow2"; + "file.file.driver=file"; + "file.file.filename=" ^ qemu_quote filename ] in + let image_opts = String.concat "," image_opts in + let cmd = QemuNBD.create image_opts in + QemuNBD.set_image_opts cmd true; + cmd + ) in QemuNBD.set_snapshot cmd false; - QemuNBD.set_format cmd (Some "qcow2"); let _, pid = QemuNBD.run_unix socket cmd in On_exit.kill pid diff --git a/output/output.mli b/output/output.mli index 8d3d686594..c1f0f53d8e 100644 --- a/output/output.mli +++ b/output/output.mli @@ -84,6 +84,7 @@ val error_if_disk_count_gt : string -> int -> unit called. *) val output_to_local_file : ?changeuid:((unit -> unit) -> unit) -> + ?compressed:bool -> Types.output_allocation -> string -> string -> int64 -> string -> unit -- 2.37.0.rc2
Richard W.M. Jones
2022-Jul-01 11:01 UTC
[Libguestfs] [PATCH v2v 2/3] -o disk, -o libvirt, -o qemu: Implement -of qcow2 -oo compressed
For various output modes, implement -oo compressed which can be used to generate compressed qcow2 files. This option was dropped when modularizing virt-v2v, and required changes to nbdcopy which are finally upstream in libnbd >= 1.13.5. Fixes: https://bugzilla.redhat.com/show_bug.cgi?id=2047660 Fixes: commit 255722cbf39afc0b012e2ac00d16fa6ba2f8c21f Reported-by: Xiaodai Wang --- TODO | 14 -------------- output/output_disk.ml | 29 +++++++++++++++++++++-------- output/output_libvirt.ml | 31 ++++++++++++++++++++++--------- output/output_qemu.ml | 25 ++++++++++++++++++------- 4 files changed, 61 insertions(+), 38 deletions(-) diff --git a/TODO b/TODO index f578d50621..04b1dd2036 100644 --- a/TODO +++ b/TODO @@ -1,17 +1,3 @@ -virt-v2v -o disk|qemu -oo compressed ------------------------------------- - -This was temporarily dropped when I modularized virt-v2v. It would -not be too difficult to add it back. The following is the qemu-nbd -command required (to be run as the output helper) which creates a -compressed qcow2 disk image: - -$ qemu-nbd --image-opts driver=compress,file.driver=qcow2,file.file.driver=file,file.file.filename=new.qcow2 - -Note this requires fixes in nbdcopy so it obeys the advertised block -alignment: -https://lists.gnu.org/archive/html/qemu-block/2022-01/threads.html#00729 - virt-v2v -o rhv-upload ---------------------- diff --git a/output/output_disk.ml b/output/output_disk.ml index bc5b4e1ca6..abcfcdc0ce 100644 --- a/output/output_disk.ml +++ b/output/output_disk.ml @@ -30,7 +30,7 @@ open Create_libvirt_xml open Output module Disk = struct - type poptions = Types.output_allocation * string * string * string + type poptions = bool * Types.output_allocation * string * string * string type t = unit @@ -41,11 +41,21 @@ module Disk = struct | None -> "" let query_output_options () - printf (f_"No output options can be used in this mode.\n") + printf (f_"Output options that can be used with -o disk: + + -oo compressed Compress the output file (used only with -of qcow2) +") let parse_options options source - if options.output_options <> [] then - error (f_"no -oo (output options) are allowed here"); + let compressed = ref false in + List.iter ( + function + | "compressed", "" -> compressed := true + | "compressed", v -> compressed := bool_of_string v + | k, _ -> + error (f_"-o disk: unknown output option ?-oo %s?") k + ) options.output_options; + if options.output_password <> None then error_option_cannot_be_used_in_output_mode "local" "-op"; @@ -60,11 +70,13 @@ module Disk = struct let output_name = Option.default source.s_name options.output_name in - options.output_alloc, options.output_format, output_name, output_storage + !compressed, options.output_alloc, options.output_format, + output_name, output_storage let setup dir options source let disks = get_disks dir in - let output_alloc, output_format, output_name, output_storage = options in + let compressed, output_alloc, output_format, output_name, output_storage + options in List.iter ( fun (i, size) -> @@ -73,11 +85,12 @@ module Disk = struct (* Create the actual output disk. *) let outdisk = disk_path output_storage output_name i in - output_to_local_file output_alloc output_format outdisk size socket + output_to_local_file ~compressed output_alloc output_format + outdisk size socket ) disks let finalize dir options () source inspect target_meta - let output_alloc, output_format, output_name, output_storage = options in + let _, output_alloc, output_format, output_name, output_storage = options in (* Convert metadata to libvirt XML. *) (match target_meta.target_firmware with diff --git a/output/output_libvirt.ml b/output/output_libvirt.ml index e0d3432d25..04b4c5f81e 100644 --- a/output/output_libvirt.ml +++ b/output/output_libvirt.ml @@ -32,7 +32,7 @@ open Create_libvirt_xml open Output module Libvirt_ = struct - type poptions = Libvirt.rw Libvirt.Connect.t Lazy.t * + type poptions = Libvirt.rw Libvirt.Connect.t Lazy.t * bool * Types.output_allocation * string * string * string type t = string * string @@ -44,11 +44,21 @@ module Libvirt_ = struct | None -> "" let query_output_options () - printf (f_"No output options can be used in this mode.\n") + printf (f_"Output options that can be used with -o libvirt: + + -oo compressed Compress the output file (used only with -of qcow2) +") let parse_options options source - if options.output_options <> [] then - error (f_"no -oo (output options) are allowed here"); + let compressed = ref false in + List.iter ( + function + | "compressed", "" -> compressed := true + | "compressed", v -> compressed := bool_of_string v + | k, _ -> + error (f_"-o disk: unknown output option ?-oo %s?") k + ) options.output_options; + if options.output_password <> None then error_option_cannot_be_used_in_output_mode "libvirt" "-op"; @@ -59,12 +69,13 @@ module Libvirt_ = struct let output_name = Option.default source.s_name options.output_name in - (conn, options.output_alloc, options.output_format, output_name, - output_pool) + (conn, !compressed, options.output_alloc, options.output_format, + output_name, output_pool) let setup dir options source let disks = get_disks dir in - let conn, output_alloc, output_format, output_name, output_pool = options in + let conn, compressed, output_alloc, output_format, + output_name, output_pool = options in let conn = Lazy.force conn in (* Get the capabilities from libvirt. *) @@ -119,13 +130,15 @@ module Libvirt_ = struct (* Create the actual output disk. *) let outdisk = target_path // output_name ^ "-sd" ^ (drive_name i) in - output_to_local_file output_alloc output_format outdisk size socket + output_to_local_file ~compressed output_alloc output_format + outdisk size socket ) disks; (capabilities_xml, pool_name) let rec finalize dir options t source inspect target_meta - let conn, output_alloc, output_format, output_name, output_pool = options in + let conn, _, output_alloc, output_format, output_name, output_pool + options in let capabilities_xml, pool_name = t in (match target_meta.target_firmware with diff --git a/output/output_qemu.ml b/output/output_qemu.ml index 3269fba501..5081b79f99 100644 --- a/output/output_qemu.ml +++ b/output/output_qemu.ml @@ -29,7 +29,8 @@ open Utils open Output module QEMU = struct - type poptions = bool * Types.output_allocation * string * string * string + type poptions = bool * bool * + Types.output_allocation * string * string * string type t = unit @@ -42,6 +43,7 @@ module QEMU = struct let query_output_options () printf (f_"Output options (-oo) which can be used with -o qemu: + -oo compressed Compress the output file (used only with -of qcow2) -oo qemu-boot Boot the guest in qemu after conversion ") @@ -49,10 +51,16 @@ module QEMU = struct if options.output_password <> None then error_option_cannot_be_used_in_output_mode "qemu" "-op"; - let qemu_boot = ref false in + let compressed = ref false + and qemu_boot = ref false in List.iter ( fun (k, v) -> match k with + | "compressed" -> + if v = "" || v = "true" then compressed := true + else if v = "false" then compressed := false + else + error (f_"-o qemu: use -oo compressed[=true|false]") | "qemu-boot" -> if v = "" || v = "true" then qemu_boot := true else if v = "false" then qemu_boot := false @@ -61,7 +69,8 @@ module QEMU = struct | k -> error (f_"-o qemu: unknown output option ?-oo %s?") k ) options.output_options; - let qemu_boot = !qemu_boot in + let compressed = !compressed + and qemu_boot = !qemu_boot in (* -os must be set to a directory. *) let output_storage @@ -74,12 +83,13 @@ module QEMU = struct let output_name = Option.default source.s_name options.output_name in - (qemu_boot, options.output_alloc, options.output_format, + (compressed, qemu_boot, options.output_alloc, options.output_format, output_name, output_storage) let setup dir options source let disks = get_disks dir in - let _, output_alloc, output_format, output_name, output_storage = options in + let compressed, _, output_alloc, output_format, + output_name, output_storage = options in List.iter ( fun (i, size) -> @@ -88,11 +98,12 @@ module QEMU = struct (* Create the actual output disk. *) let outdisk = disk_path output_storage output_name i in - output_to_local_file output_alloc output_format outdisk size socket + output_to_local_file ~compressed output_alloc output_format + outdisk size socket ) disks let finalize dir options () source inspect target_meta - let qemu_boot, output_alloc, output_format, + let _, qemu_boot, output_alloc, output_format, output_name, output_storage = options in let { guestcaps; target_buses; target_firmware } = target_meta in -- 2.37.0.rc2
Richard W.M. Jones
2022-Jul-01 11:01 UTC
[Libguestfs] [PATCH v2v 3/3] tests: Add a simple test of -o local -of qcow2 -oo compressed
This only tests that it doesn't completely fail, which it did before we fixed nbdcopy. I checked the file sizes manually and with compression the resulting file is about half the size. --- tests/Makefile.am | 2 + tests/test-v2v-o-local-qcow2-compressed.sh | 51 ++++++++++++++++++++++ tests/test-v2v-of-option.sh | 2 + 3 files changed, 55 insertions(+) diff --git a/tests/Makefile.am b/tests/Makefile.am index ebc433ae5e..fb068624c7 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -83,6 +83,7 @@ TESTS = \ test-v2v-networks-and-bridges.sh \ test-v2v-o-glance.sh \ test-v2v-o-libvirt.sh \ + test-v2v-o-local-qcow2-compressed.sh \ test-v2v-o-null.sh \ test-v2v-o-openstack.sh \ test-v2v-o-qemu.sh \ @@ -242,6 +243,7 @@ EXTRA_DIST += \ test-v2v-networks-and-bridges-expected.xml \ test-v2v-o-glance.sh \ test-v2v-o-libvirt.sh \ + test-v2v-o-local-qcow2-compressed.sh \ test-v2v-o-null.sh \ test-v2v-o-openstack.sh \ test-v2v-o-qemu.sh \ diff --git a/tests/test-v2v-o-local-qcow2-compressed.sh b/tests/test-v2v-o-local-qcow2-compressed.sh new file mode 100755 index 0000000000..6ebf92c976 --- /dev/null +++ b/tests/test-v2v-o-local-qcow2-compressed.sh @@ -0,0 +1,51 @@ +#!/bin/bash - +# libguestfs virt-v2v test script +# Copyright (C) 2014-2022 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 Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +# Test -o local -of qcow2 -oo compressed. + +set -e + +source ./functions.sh +set -e +set -x + +skip_if_skipped +requires test -f ../test-data/phony-guests/windows.img + +# This requires fixed nbdcopy >= 1.13.5. +requires nbdcopy --version +version="$( nbdcopy --version | head -1 | awk '{print $2}' )" +minor="$( echo "$version" | awk -F. '{print $2}' )" +release="$( echo "$version" | awk -F. '{print $3}' )" +requires test $minor -gt 13 -o \( $minor -eq 13 -a $release -ge 5 \) + +export VIRT_TOOLS_DATA_DIR="$srcdir/../test-data/fake-virt-tools" + +d=test-v2v-o-local-qcow2-compressed.d +rm -rf $d +cleanup_fn rm -rf $d +mkdir $d + +$VG virt-v2v --debug-gc \ + -i disk ../test-data/phony-guests/windows.img \ + -o local -of qcow2 -oo compressed -os $d + +# Test the libvirt XML metadata and a disk was created. +ls -l $d +test -f $d/windows.xml +test -f $d/windows-sda diff --git a/tests/test-v2v-of-option.sh b/tests/test-v2v-of-option.sh index bdfd34180d..6c5f5938c8 100755 --- a/tests/test-v2v-of-option.sh +++ b/tests/test-v2v-of-option.sh @@ -42,6 +42,8 @@ $VG virt-v2v --debug-gc \ -i libvirt -ic "$libvirt_uri" windows \ -o local -os $d -of qcow2 +ls -l $d + # Test the disk is qcow2 format. if [ "$(guestfish disk-format $d/windows-sda)" != qcow2 ]; then echo "$0: test failed: output is not qcow2" -- 2.37.0.rc2