[Libguestfs] [PATCH v7 1/1] v2v: ova: don't extract files from OVA if it's not needed
Richard W.M. Jones
rjones at redhat.com
Fri Feb 3 15:33:27 UTC 2017
On Fri, Feb 03, 2017 at 02:58:36AM +0100, Tomáš Golembiovský wrote:
> We don't have to always extract all files from the OVA archive. The OVA,
> as defined in the standard, is plain tar. We can work directly over the
> tar archive if we use correct 'offset' and 'size' options when defining
> the backing file for QEMU. This puts much lower requirement on available
> disk space.
>
> Since the virt-v2v behaviour for OVA input now depends on QEMU version
> available this affects some of the tests. Expected result of the
> affected also has to depend on the QEMU used thus such tests will have
> two *.expected files.
>
> Signed-off-by: Tomáš Golembiovský <tgolembi at redhat.com>
> ---
> mllib/common_utils.ml | 9 +++
> mllib/common_utils.mli | 10 +++
> mllib/common_utils_tests.ml | 7 ++
> test-data/test-utils.sh | 19 +++++
> v2v/Makefile.am | 2 +
> v2v/input_ova.ml | 109 +++++++++++++++++++++----
> v2v/test-v2v-i-ova-formats.sh | 5 +-
> v2v/test-v2v-i-ova-subfolders.expected2 | 18 +++++
> v2v/test-v2v-i-ova-subfolders.sh | 13 ++-
> v2v/test-v2v-i-ova-tar.expected | 18 +++++
> v2v/test-v2v-i-ova-tar.expected2 | 18 +++++
> v2v/test-v2v-i-ova-tar.ovf | 138 ++++++++++++++++++++++++++++++++
> v2v/test-v2v-i-ova-tar.sh | 72 +++++++++++++++++
> v2v/test-v2v-i-ova-two-disks.expected2 | 19 +++++
> v2v/test-v2v-i-ova-two-disks.sh | 13 ++-
> v2v/utils.ml | 72 +++++++++++++++++
> v2v/utils.mli | 12 +++
ACK .. but it would be nice to split up this megapatch ...
We could have at least v2v/utils.* and mllib/common_utils.*
into separate commits (making 3 or even more commits in all).
Rich.
> 17 files changed, 530 insertions(+), 24 deletions(-)
> create mode 100644 v2v/test-v2v-i-ova-subfolders.expected2
> create mode 100644 v2v/test-v2v-i-ova-tar.expected
> create mode 100644 v2v/test-v2v-i-ova-tar.expected2
> create mode 100644 v2v/test-v2v-i-ova-tar.ovf
> create mode 100755 v2v/test-v2v-i-ova-tar.sh
> create mode 100644 v2v/test-v2v-i-ova-two-disks.expected2
>
> diff --git a/mllib/common_utils.ml b/mllib/common_utils.ml
> index e9ae6a4a2..a79abdd7e 100644
> --- a/mllib/common_utils.ml
> +++ b/mllib/common_utils.ml
> @@ -236,6 +236,15 @@ end
> let (//) = Filename.concat
> let quote = Filename.quote
>
> +let subdirectory parent path =
> + if path = parent then
> + ""
> + else if String.is_prefix path (parent // "") then
> + let len = String.length parent in
> + String.sub path (len+1) (String.length path - len-1)
> + else
> + raise (Invalid_argument (sprintf "%S is not a path prefix of %S" parent path))
> +
> let ( +^ ) = Int64.add
> let ( -^ ) = Int64.sub
> let ( *^ ) = Int64.mul
> diff --git a/mllib/common_utils.mli b/mllib/common_utils.mli
> index 722e528e5..977ce6576 100644
> --- a/mllib/common_utils.mli
> +++ b/mllib/common_utils.mli
> @@ -116,6 +116,16 @@ val ( // ) : string -> string -> string
> val quote : string -> string
> (** Shell-safe quoting of a string (alias for {!Filename.quote}). *)
>
> +val subdirectory : string -> string -> string
> +(** [subdirectory parent path] returns subdirectory part of [path] relative
> + to the [parent]. If [path] and [parent] point to the same directory empty
> + string is returned.
> +
> + Note: path normalization on arguments is NOT performed!
> +
> + If [parent] is not a path prefix of [path] the function raises
> + [Invalid_argument]. *)
> +
> val ( +^ ) : int64 -> int64 -> int64
> val ( -^ ) : int64 -> int64 -> int64
> val ( *^ ) : int64 -> int64 -> int64
> diff --git a/mllib/common_utils_tests.ml b/mllib/common_utils_tests.ml
> index 77b0524c1..aacc01e04 100644
> --- a/mllib/common_utils_tests.ml
> +++ b/mllib/common_utils_tests.ml
> @@ -27,6 +27,12 @@ let assert_equal_int = assert_equal ~printer:(fun x -> string_of_int x)
> let assert_equal_int64 = assert_equal ~printer:(fun x -> Int64.to_string x)
> let assert_equal_stringlist = assert_equal ~printer:(fun x -> "(" ^ (String.escaped (String.concat "," x)) ^ ")")
>
> +let test_subdirectory ctx =
> + assert_equal_string "" (subdirectory "/foo" "/foo");
> + assert_equal_string "" (subdirectory "/foo" "/foo/");
> + assert_equal_string "bar" (subdirectory "/foo" "/foo/bar");
> + assert_equal_string "bar/baz" (subdirectory "/foo" "/foo/bar/baz")
> +
> (* Test Common_utils.int_of_le32 and Common_utils.le32_of_int. *)
> let test_le32 ctx =
> assert_equal_int64 0x20406080L (int_of_le32 "\x80\x60\x40\x20");
> @@ -129,6 +135,7 @@ let test_string_lines_split ctx =
> let suite =
> "mllib Common_utils" >:::
> [
> + "subdirectory" >:: test_subdirectory;
> "numeric.le32" >:: test_le32;
> "sizes.parse_resize" >:: test_parse_resize;
> "sizes.human_size" >:: test_human_size;
> diff --git a/test-data/test-utils.sh b/test-data/test-utils.sh
> index 86a5aaf12..1c4abe392 100755
> --- a/test-data/test-utils.sh
> +++ b/test-data/test-utils.sh
> @@ -54,3 +54,22 @@ do_sha256 ()
> ;;
> esac
> }
> +
> +# Returns 0 if QEMU version is greater or equal to the arguments
> +qemu_is_version() {
> + if [ $# -ne 2 ] ; then
> + echo "Usage: $0 <major_version> <minor_version>" >&2
> + return 3
> + fi
> +
> +
> + [[ "$(qemu-img --version)" =~ 'qemu-img version '([0-9]+)\.([0-9]+) ]] || return 2
> + QMAJ=${BASH_REMATCH[1]}
> + QMIN=${BASH_REMATCH[2]}
> +
> + if [ \( $QMAJ -gt $1 \) -o \( $QMAJ -eq $1 -a $QMIN -ge $2 \) ] ; then
> + return 0
> + fi
> +
> + return 1
> +}
> diff --git a/v2v/Makefile.am b/v2v/Makefile.am
> index 9189aaf12..d62ac477e 100644
> --- a/v2v/Makefile.am
> +++ b/v2v/Makefile.am
> @@ -260,6 +260,7 @@ TESTS_ENVIRONMENT = $(top_builddir)/run --test
>
> TESTS = \
> test-v2v-docs.sh \
> + test-v2v-i-ova-tar.sh \
> test-v2v-i-ova-formats.sh \
> test-v2v-i-ova-gz.sh \
> test-v2v-i-ova-subfolders.sh \
> @@ -359,6 +360,7 @@ EXTRA_DIST += \
> test-v2v-i-ova-subfolders.expected \
> test-v2v-i-ova-subfolders.ovf \
> test-v2v-i-ova-subfolders.sh \
> + test-v2v-i-ova-tar.sh \
> test-v2v-i-ova-two-disks.expected \
> test-v2v-i-ova-two-disks.ovf \
> test-v2v-i-ova-two-disks.sh \
> diff --git a/v2v/input_ova.ml b/v2v/input_ova.ml
> index 40f723633..d2b48c180 100644
> --- a/v2v/input_ova.ml
> +++ b/v2v/input_ova.ml
> @@ -39,17 +39,23 @@ object
>
> method source () =
>
> - let untar ?(format = "") file outdir =
> - let cmd = [ "tar"; sprintf "-x%sf" format; file; "-C"; outdir ] in
> + (* Untar part or all files from tar archive. If [paths] is specified it is
> + * a list of paths in the tar archive.
> + *)
> + let untar ?(format = "") ?paths file outdir =
> + let cmd =
> + [ "tar"; sprintf "-x%sf" format; file; "-C"; outdir ]
> + @ match paths with None -> [] | Some p -> p in
> if run_command cmd <> 0 then
> - error (f_"error unpacking %s, see earlier error messages") ova in
> + error (f_"error unpacking %s, see earlier error messages") ova
> + in
>
> (* Extract ova file. *)
> - let exploded =
> + let exploded, partial =
> (* The spec allows a directory to be specified as an ova. This
> * is also pretty convenient.
> *)
> - if is_directory ova then ova
> + if is_directory ova then ova, false
> else (
> let uncompress_head zcat file =
> let cmd = sprintf "%s %s" zcat (quote file) in
> @@ -67,11 +73,35 @@ object
>
> tmpfile in
>
> + (* Untar only ovf and manifest from the archive *)
> + let untar_metadata ova outdir =
> + let files =
> + external_command (sprintf "tar -tf %s" (Filename.quote ova)) in
> + let files =
> + filter_map (fun f ->
> + if Filename.check_suffix f ".ovf" ||
> + Filename.check_suffix f ".mf" then
> + Some f
> + else None
> + ) files in
> + untar ~paths:files ova outdir
> + in
> +
> match detect_file_type ova with
> | `Tar ->
> (* Normal ovas are tar file (not compressed). *)
> - untar ova tmpdir;
> - tmpdir
> + let qmajor, qminor = qemu_img_version () in
> + if qmajor > 2 || (qmajor == 2 && qminor >= 8) then (
> + (* If QEMU is 2.8 or newer we don't have to extract everything.
> + * We can access disks inside the tar archive directly.
> + *)
> + untar_metadata ova tmpdir;
> + tmpdir, true
> + ) else (
> + untar ova tmpdir;
> + tmpdir, false
> + )
> +
> | `Zip ->
> (* However, although not permitted by the spec, people ship
> * zip files as ova too.
> @@ -81,7 +111,7 @@ object
> [ "-j"; "-d"; tmpdir; ova ] in
> if run_command cmd <> 0 then
> error (f_"error unpacking %s, see earlier error messages") ova;
> - tmpdir
> + tmpdir, false
> | (`GZip|`XZ) as format ->
> let zcat, tar_fmt =
> match format with
> @@ -94,7 +124,7 @@ object
> (match tmpfiletype with
> | `Tar ->
> untar ~format:tar_fmt ova tmpdir;
> - tmpdir
> + tmpdir, false
> | `Zip | `GZip | `XZ | `Unknown ->
> error (f_"%s: unsupported file format\n\nFormats which we currently understand for '-i ova' are: tar (uncompressed, compress with gzip or xz), zip") ova
> )
> @@ -152,6 +182,7 @@ object
> fun mf ->
> debug "processing manifest %s" mf;
> let mf_folder = Filename.dirname mf in
> + let mf_subfolder = subdirectory exploded mf_folder in
> let chan = open_in mf in
> let rec loop () =
> let line = input_line chan in
> @@ -160,7 +191,11 @@ object
> let disk = Str.matched_group 2 line in
> let expected = Str.matched_group 3 line in
> let csum = Checksums.of_string mode expected in
> - try Checksums.verify_checksum csum (mf_folder // disk)
> + try
> + if partial then
> + Checksums.verify_checksum csum ~tar:ova (mf_subfolder // disk)
> + else
> + Checksums.verify_checksum csum (mf_folder // disk)
> with Checksums.Mismatched_checksum (_, actual) ->
> error (f_"checksum of disk %s does not match manifest %s (actual %s(%s) = %s, expected %s(%s) = %s)")
> disk mf mode disk actual mode disk expected;
> @@ -283,9 +318,25 @@ object
> | Some "gzip" -> true
> | Some s -> error (f_"unsupported compression in OVF: %s") s in
>
> - (* Does the file exist and is it readable? *)
> - let filename = ovf_folder // filename in
> - Unix.access filename [Unix.R_OK];
> + let partial =
> + if compressed && partial then (
> + (* We cannot access compressed disk inside the tar; we have to
> + * extract it *)
> + untar ~paths:[(subdirectory exploded ovf_folder) // filename]
> + ova tmpdir;
> + false
> + )
> + else
> + partial in
> +
> + let filename =
> + if partial then
> + (subdirectory exploded ovf_folder) // filename
> + else (
> + (* Does the file exist and is it readable? *)
> + Unix.access (ovf_folder // filename) [Unix.R_OK];
> + ovf_folder // filename
> + ) in
>
> (* The spec allows the file to be gzip-compressed, in which case
> * we must uncompress it into the tmpdir.
> @@ -302,9 +353,39 @@ object
> )
> else filename in
>
> + let qemu_uri =
> + if not partial then (
> + filename
> + )
> + else (
> + let offset, size =
> + try find_file_in_tar ova filename
> + with
> + | Not_found ->
> + error (f_"file '%s' not found in the ova") filename
> + | Failure msg -> error (f_"%s") msg in
> + (* QEMU requires size aligned to 512 bytes. This is safe because
> + * tar also works with 512 byte blocks.
> + *)
> + let size = roundup64 size 512L in
> + let doc = [
> + "file", JSON.Dict [
> + "driver", JSON.String "raw";
> + "offset", JSON.Int64 offset;
> + "size", JSON.Int64 size;
> + "file", JSON.Dict [
> + "filename", JSON.String ova]
> + ]
> + ] in
> + let uri =
> + sprintf "json:%s" (JSON.string_of_doc ~fmt:JSON.Compact doc) in
> + debug "json: %s" uri;
> + uri
> + ) in
> +
> let disk = {
> s_disk_id = i;
> - s_qemu_uri = filename;
> + s_qemu_uri = qemu_uri;
> s_format = Some "vmdk";
> s_controller = controller;
> } in
> diff --git a/v2v/test-v2v-i-ova-formats.sh b/v2v/test-v2v-i-ova-formats.sh
> index bd3e30ee8..0eec600eb 100755
> --- a/v2v/test-v2v-i-ova-formats.sh
> +++ b/v2v/test-v2v-i-ova-formats.sh
> @@ -22,7 +22,7 @@ unset CDPATH
> export LANG=C
> set -e
>
> -formats="tar zip tar-gz tar-xz"
> +formats="zip tar-gz tar-xz"
>
> if [ -n "$SKIP_TEST_V2V_I_OVA_FORMATS_SH" ]; then
> echo "$0: test skipped because environment variable is set"
> @@ -63,9 +63,6 @@ cp ../test-v2v-i-ova-formats.ovf .
>
> for format in $formats; do
> case "$format" in
> - tar)
> - tar -cf test-$format.ova test-v2v-i-ova-formats.ovf disk1.vmdk disk1.mf
> - ;;
> zip)
> zip -r test test-v2v-i-ova-formats.ovf disk1.vmdk disk1.mf
> mv test.zip test-$format.ova
> diff --git a/v2v/test-v2v-i-ova-subfolders.expected2 b/v2v/test-v2v-i-ova-subfolders.expected2
> new file mode 100644
> index 000000000..87996d202
> --- /dev/null
> +++ b/v2v/test-v2v-i-ova-subfolders.expected2
> @@ -0,0 +1,18 @@
> +Source guest information (--print-source option):
> +
> + source name: 2K8R2EESP1_2_Medium
> +hypervisor type: vmware
> + memory: 1073741824 (bytes)
> + nr vCPUs: 1
> + CPU features:
> + firmware: uefi
> + display:
> + video:
> + sound:
> +disks:
> + json:{ "file": { "driver": "raw", "offset": 2048, "size": 10240, "file": { "filename": "test.ova" } } } (vmdk) [scsi]
> +removable media:
> + CD-ROM [ide] in slot 0
> +NICs:
> + Network "Network adapter 1"
> +
> diff --git a/v2v/test-v2v-i-ova-subfolders.sh b/v2v/test-v2v-i-ova-subfolders.sh
> index 4fd1acea3..422959036 100755
> --- a/v2v/test-v2v-i-ova-subfolders.sh
> +++ b/v2v/test-v2v-i-ova-subfolders.sh
> @@ -56,10 +56,17 @@ popd
> # normalize the output.
> $VG virt-v2v --debug-gc --quiet \
> -i ova $d/test.ova \
> - --print-source |
> -sed 's,[^ \t]*\(subfolder/disk.*\.vmdk\),\1,' > $d/source
> + --print-source > $d/source
>
> # Check the parsed source is what we expect.
> -diff -u test-v2v-i-ova-subfolders.expected $d/source
> +if qemu_is_version 2 8 ; then
> + # normalize the output
> + sed -i -e "s,\"$d/,\"," $d/source
> + diff -u test-v2v-i-ova-subfolders.expected2 $d/source
> +else
> + # normalize the output
> + sed -i -e 's,[^ \t]*\(subfolder/disk.*\.vmdk\),\1,' $d/source
> + diff -u test-v2v-i-ova-subfolders.expected $d/source
> +fi
>
> rm -rf $d
> diff --git a/v2v/test-v2v-i-ova-tar.expected b/v2v/test-v2v-i-ova-tar.expected
> new file mode 100644
> index 000000000..7049aeecc
> --- /dev/null
> +++ b/v2v/test-v2v-i-ova-tar.expected
> @@ -0,0 +1,18 @@
> +Source guest information (--print-source option):
> +
> + source name: 2K8R2EESP1_2_Medium
> +hypervisor type: vmware
> + memory: 1073741824 (bytes)
> + nr vCPUs: 1
> + CPU features:
> + firmware: uefi
> + display:
> + video:
> + sound:
> +disks:
> + disk1.vmdk (vmdk) [scsi]
> +removable media:
> + CD-ROM [ide] in slot 0
> +NICs:
> + Network "Network adapter 1"
> +
> diff --git a/v2v/test-v2v-i-ova-tar.expected2 b/v2v/test-v2v-i-ova-tar.expected2
> new file mode 100644
> index 000000000..1aa54dc37
> --- /dev/null
> +++ b/v2v/test-v2v-i-ova-tar.expected2
> @@ -0,0 +1,18 @@
> +Source guest information (--print-source option):
> +
> + source name: 2K8R2EESP1_2_Medium
> +hypervisor type: vmware
> + memory: 1073741824 (bytes)
> + nr vCPUs: 1
> + CPU features:
> + firmware: uefi
> + display:
> + video:
> + sound:
> +disks:
> + json:{ "file": { "driver": "raw", "offset": 9216, "size": 10240, "file": { "filename": "test-tar.ova" } } } (vmdk) [scsi]
> +removable media:
> + CD-ROM [ide] in slot 0
> +NICs:
> + Network "Network adapter 1"
> +
> diff --git a/v2v/test-v2v-i-ova-tar.ovf b/v2v/test-v2v-i-ova-tar.ovf
> new file mode 100644
> index 000000000..4827c7e9b
> --- /dev/null
> +++ b/v2v/test-v2v-i-ova-tar.ovf
> @@ -0,0 +1,138 @@
> +<?xml version="1.0" encoding="UTF-8"?>
> +<Envelope vmw:buildId="build-1750787" xmlns="http://schemas.dmtf.org/ovf/envelope/1" xmlns:cim="http://schemas.dmtf.org/wbem/wscim/1/common" xmlns:ovf="http://schemas.dmtf.org/ovf/envelope/1" xmlns:rasd="http://schemas.dmtf.org/wbem/wscim/1/cim-schema/2/CIM_ResourceAllocationSettingData" xmlns:vmw="http://www.vmware.com/schema/ovf" xmlns:vssd="http://schemas.dmtf.org/wbem/wscim/1/cim-schema/2/CIM_VirtualSystemSettingData" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
> + <References>
> + <File ovf:href="disk1.vmdk" ovf:id="file1" ovf:size="7804077568"/>
> + </References>
> + <DiskSection>
> + <Info>Virtual disk information</Info>
> + <Disk ovf:capacity="50" ovf:capacityAllocationUnits="byte * 2^30" ovf:diskId="vmdisk1" ovf:fileRef="file1" ovf:format="http://www.vmware.com/interfaces/specifications/vmdk.html#streamOptimized" ovf:populatedSize="18975752192"/>
> + </DiskSection>
> + <NetworkSection>
> + <Info>The list of logical networks</Info>
> + <Network ovf:name="PG-VLAN60">
> + <Description>The PG-VLAN60 network</Description>
> + </Network>
> + </NetworkSection>
> + <VirtualSystem ovf:id="2K8R2EESP1_2_Medium">
> + <Info>A virtual machine</Info>
> + <Name>2K8R2EESP1_2_Medium</Name>
> + <OperatingSystemSection ovf:id="103" vmw:osType="windows7Server64Guest">
> + <Info>The kind of installed guest operating system</Info>
> + <Description>Microsoft Windows Server 2008 R2 (64-bit)</Description>
> + </OperatingSystemSection>
> + <VirtualHardwareSection>
> + <Info>Virtual hardware requirements</Info>
> + <System>
> + <vssd:ElementName>Virtual Hardware Family</vssd:ElementName>
> + <vssd:InstanceID>0</vssd:InstanceID>
> + <vssd:VirtualSystemIdentifier>2K8R2EESP1_2_Medium</vssd:VirtualSystemIdentifier>
> + <vssd:VirtualSystemType>vmx-10</vssd:VirtualSystemType>
> + </System>
> + <Item>
> + <rasd:AllocationUnits>hertz * 10^6</rasd:AllocationUnits>
> + <rasd:Description>Number of Virtual CPUs</rasd:Description>
> + <rasd:ElementName>1 virtual CPU(s)</rasd:ElementName>
> + <rasd:InstanceID>1</rasd:InstanceID>
> + <rasd:ResourceType>3</rasd:ResourceType>
> + <rasd:VirtualQuantity>1</rasd:VirtualQuantity>
> + </Item>
> + <Item>
> + <rasd:AllocationUnits>byte * 2^20</rasd:AllocationUnits>
> + <rasd:Description>Memory Size</rasd:Description>
> + <rasd:ElementName>1024MB of memory</rasd:ElementName>
> + <rasd:InstanceID>2</rasd:InstanceID>
> + <rasd:ResourceType>4</rasd:ResourceType>
> + <rasd:VirtualQuantity>1024</rasd:VirtualQuantity>
> + </Item>
> + <Item>
> + <rasd:Address>0</rasd:Address>
> + <rasd:Description>SCSI Controller</rasd:Description>
> + <rasd:ElementName>SCSI controller 0</rasd:ElementName>
> + <rasd:InstanceID>3</rasd:InstanceID>
> + <rasd:ResourceSubType>lsilogicsas</rasd:ResourceSubType>
> + <rasd:ResourceType>6</rasd:ResourceType>
> + <vmw:Config ovf:required="false" vmw:key="slotInfo.pciSlotNumber" vmw:value="160"/>
> + </Item>
> + <Item>
> + <rasd:Address>1</rasd:Address>
> + <rasd:Description>IDE Controller</rasd:Description>
> + <rasd:ElementName>IDE 1</rasd:ElementName>
> + <rasd:InstanceID>4</rasd:InstanceID>
> + <rasd:ResourceType>5</rasd:ResourceType>
> + </Item>
> + <Item>
> + <rasd:Address>0</rasd:Address>
> + <rasd:Description>IDE Controller</rasd:Description>
> + <rasd:ElementName>IDE 0</rasd:ElementName>
> + <rasd:InstanceID>5</rasd:InstanceID>
> + <rasd:ResourceType>5</rasd:ResourceType>
> + </Item>
> + <Item ovf:required="false">
> + <rasd:AutomaticAllocation>false</rasd:AutomaticAllocation>
> + <rasd:ElementName>Video card</rasd:ElementName>
> + <rasd:InstanceID>6</rasd:InstanceID>
> + <rasd:ResourceType>24</rasd:ResourceType>
> + <vmw:Config ovf:required="false" vmw:key="enable3DSupport" vmw:value="false"/>
> + <vmw:Config ovf:required="false" vmw:key="use3dRenderer" vmw:value="automatic"/>
> + <vmw:Config ovf:required="false" vmw:key="useAutoDetect" vmw:value="true"/>
> + <vmw:Config ovf:required="false" vmw:key="videoRamSizeInKB" vmw:value="4096"/>
> + </Item>
> + <Item ovf:required="false">
> + <rasd:AutomaticAllocation>false</rasd:AutomaticAllocation>
> + <rasd:ElementName>VMCI device</rasd:ElementName>
> + <rasd:InstanceID>7</rasd:InstanceID>
> + <rasd:ResourceSubType>vmware.vmci</rasd:ResourceSubType>
> + <rasd:ResourceType>1</rasd:ResourceType>
> + <vmw:Config ovf:required="false" vmw:key="allowUnrestrictedCommunication" vmw:value="false"/>
> + <vmw:Config ovf:required="false" vmw:key="slotInfo.pciSlotNumber" vmw:value="32"/>
> + </Item>
> + <Item ovf:required="false">
> + <rasd:AddressOnParent>0</rasd:AddressOnParent>
> + <rasd:AutomaticAllocation>false</rasd:AutomaticAllocation>
> + <rasd:ElementName>CD/DVD drive 1</rasd:ElementName>
> + <rasd:InstanceID>8</rasd:InstanceID>
> + <rasd:Parent>4</rasd:Parent>
> + <rasd:ResourceSubType>vmware.cdrom.atapi</rasd:ResourceSubType>
> + <rasd:ResourceType>15</rasd:ResourceType>
> + </Item>
> + <Item>
> + <rasd:AddressOnParent>0</rasd:AddressOnParent>
> + <rasd:ElementName>Hard disk 1</rasd:ElementName>
> + <rasd:HostResource>ovf:/disk/vmdisk1</rasd:HostResource>
> + <rasd:InstanceID>9</rasd:InstanceID>
> + <rasd:Parent>3</rasd:Parent>
> + <rasd:ResourceType>17</rasd:ResourceType>
> + <vmw:Config ovf:required="false" vmw:key="backing.writeThrough" vmw:value="false"/>
> + </Item>
> + <Item>
> + <rasd:AddressOnParent>7</rasd:AddressOnParent>
> + <rasd:AutomaticAllocation>true</rasd:AutomaticAllocation>
> + <rasd:Connection>PG-VLAN60</rasd:Connection>
> + <rasd:Description>E1000 ethernet adapter on "PG-VLAN60"</rasd:Description>
> + <rasd:ElementName>Network adapter 1</rasd:ElementName>
> + <rasd:InstanceID>11</rasd:InstanceID>
> + <rasd:ResourceSubType>E1000</rasd:ResourceSubType>
> + <rasd:ResourceType>10</rasd:ResourceType>
> + <vmw:Config ovf:required="false" vmw:key="slotInfo.pciSlotNumber" vmw:value="33"/>
> + <vmw:Config ovf:required="false" vmw:key="wakeOnLanEnabled" vmw:value="true"/>
> + </Item>
> + <vmw:Config ovf:required="false" vmw:key="cpuHotAddEnabled" vmw:value="false"/>
> + <vmw:Config ovf:required="false" vmw:key="cpuHotRemoveEnabled" vmw:value="false"/>
> + <vmw:Config ovf:required="false" vmw:key="firmware" vmw:value="efi"/>
> + <vmw:Config ovf:required="false" vmw:key="virtualICH7MPresent" vmw:value="false"/>
> + <vmw:Config ovf:required="false" vmw:key="virtualSMCPresent" vmw:value="false"/>
> + <vmw:Config ovf:required="false" vmw:key="memoryHotAddEnabled" vmw:value="false"/>
> + <vmw:Config ovf:required="false" vmw:key="nestedHVEnabled" vmw:value="false"/>
> + <vmw:Config ovf:required="false" vmw:key="powerOpInfo.powerOffType" vmw:value="soft"/>
> + <vmw:Config ovf:required="false" vmw:key="powerOpInfo.resetType" vmw:value="soft"/>
> + <vmw:Config ovf:required="false" vmw:key="powerOpInfo.standbyAction" vmw:value="checkpoint"/>
> + <vmw:Config ovf:required="false" vmw:key="powerOpInfo.suspendType" vmw:value="hard"/>
> + <vmw:Config ovf:required="false" vmw:key="tools.afterPowerOn" vmw:value="true"/>
> + <vmw:Config ovf:required="false" vmw:key="tools.afterResume" vmw:value="true"/>
> + <vmw:Config ovf:required="false" vmw:key="tools.beforeGuestShutdown" vmw:value="true"/>
> + <vmw:Config ovf:required="false" vmw:key="tools.beforeGuestStandby" vmw:value="true"/>
> + <vmw:Config ovf:required="false" vmw:key="tools.syncTimeWithHost" vmw:value="false"/>
> + <vmw:Config ovf:required="false" vmw:key="tools.toolsUpgradePolicy" vmw:value="upgradeAtPowerCycle"/>
> + </VirtualHardwareSection>
> + </VirtualSystem>
> +</Envelope>
> diff --git a/v2v/test-v2v-i-ova-tar.sh b/v2v/test-v2v-i-ova-tar.sh
> new file mode 100755
> index 000000000..c3b0588f4
> --- /dev/null
> +++ b/v2v/test-v2v-i-ova-tar.sh
> @@ -0,0 +1,72 @@
> +#!/bin/bash -
> +# libguestfs virt-v2v test script
> +# Copyright (C) 2014-2016 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 -i ova option with ova file compressed in different ways
> +
> +unset CDPATH
> +export LANG=C
> +set -e
> +
> +if [ -n "$SKIP_TEST_V2V_I_OVA_FORMATS_SH" ]; then
> + echo "$0: test skipped because environment variable is set"
> + exit 77
> +fi
> +
> +if [ "$(guestfish get-backend)" = "uml" ]; then
> + echo "$0: test skipped because UML backend does not support network"
> + exit 77
> +fi
> +
> +export VIRT_TOOLS_DATA_DIR="$srcdir/../test-data/fake-virt-tools"
> +
> +. $srcdir/../test-data/test-utils.sh
> +
> +d=test-v2v-i-ova-tar.d
> +rm -rf $d
> +mkdir $d
> +
> +pushd $d
> +
> +# Create a phony OVA. This is only a test of source parsing, not
> +# conversion, so the contents of the disks doesn't matter.
> +truncate -s 10k disk1.vmdk
> +sha=`do_sha1 disk1.vmdk`
> +echo -e "SHA1(disk1.vmdk)= $sha\r" > disk1.mf
> +cp ../test-v2v-i-ova-tar.ovf .
> +tar -cf test-tar.ova test-v2v-i-ova-tar.ovf disk1.vmdk disk1.mf
> +
> +popd
> +
> +# Run virt-v2v but only as far as the --print-source stage
> +$VG virt-v2v --debug-gc --quiet \
> + -i ova $d/test-tar.ova \
> + --print-source > $d/source
> +
> +# Check the parsed source is what we expect.
> +if qemu_is_version 2 8 ; then
> + # normalize the output
> + sed -i -e "s,\"$d/,\"," $d/source
> + diff -u test-v2v-i-ova-tar.expected2 $d/source
> +else
> + # normalize the output
> + sed -i -e 's,[^ \t]*\(disk.*.vmdk\),\1,' $d/source
> + diff -u test-v2v-i-ova-tar.expected $d/source
> +fi
> +
> +
> +rm -rf $d
> diff --git a/v2v/test-v2v-i-ova-two-disks.expected2 b/v2v/test-v2v-i-ova-two-disks.expected2
> new file mode 100644
> index 000000000..b12ca1bd6
> --- /dev/null
> +++ b/v2v/test-v2v-i-ova-two-disks.expected2
> @@ -0,0 +1,19 @@
> +Source guest information (--print-source option):
> +
> + source name: 2K8R2EESP1_2_Medium
> +hypervisor type: vmware
> + memory: 1073741824 (bytes)
> + nr vCPUs: 1
> + CPU features:
> + firmware: bios
> + display:
> + video:
> + sound:
> +disks:
> + json:{ "file": { "driver": "raw", "offset": 9728, "size": 10240, "file": { "filename": "test.ova" } } } (vmdk) [scsi]
> + json:{ "file": { "driver": "raw", "offset": 21504, "size": 102400, "file": { "filename": "test.ova" } } } (vmdk) [scsi]
> +removable media:
> + CD-ROM [ide] in slot 0
> +NICs:
> + Network "Network adapter 1"
> +
> diff --git a/v2v/test-v2v-i-ova-two-disks.sh b/v2v/test-v2v-i-ova-two-disks.sh
> index 26dd19860..310aff1b9 100755
> --- a/v2v/test-v2v-i-ova-two-disks.sh
> +++ b/v2v/test-v2v-i-ova-two-disks.sh
> @@ -60,10 +60,17 @@ popd
> # normalize the output.
> $VG virt-v2v --debug-gc --quiet \
> -i ova $d/test.ova \
> - --print-source |
> -sed 's,[^ \t]*\(disk.*.vmdk\),\1,' > $d/source
> + --print-source > $d/source
>
> # Check the parsed source is what we expect.
> -diff -u test-v2v-i-ova-two-disks.expected $d/source
> +if qemu_is_version 2 8 ; then
> + # normalize the output
> + sed -i -e "s,\"$d/,\"," $d/source
> + diff -u test-v2v-i-ova-two-disks.expected2 $d/source
> +else
> + # normalize the output
> + sed -i -e 's,[^ \t]*\(disk.*.vmdk\),\1,' $d/source
> + diff -u test-v2v-i-ova-two-disks.expected $d/source
> +fi
>
> rm -rf $d
> diff --git a/v2v/utils.ml b/v2v/utils.ml
> index 17ad8a29c..111dc0ea9 100644
> --- a/v2v/utils.ml
> +++ b/v2v/utils.ml
> @@ -90,3 +90,75 @@ let du filename =
> match lines with
> | line::_ -> Int64.of_string line
> | [] -> invalid_arg filename
> +
> +let qemu_img_version () =
> + let lines = external_command "qemu-img --version" in
> + match lines with
> + | [] -> error ("'qemu-img --version' returned no output")
> + | line :: _ ->
> + let rex = Str.regexp
> + "qemu-img version \\([0-9]+\\)\\.\\([0-9]+\\)" in
> + if Str.string_match rex line 0 then (
> + try
> + int_of_string (Str.matched_group 1 line),
> + int_of_string (Str.matched_group 2 line)
> + with Failure _ ->
> + warning (f_"failed to parse qemu-img version(%S), assuming 0.9")
> + line;
> + 0, 9
> + ) else (
> + warning (f_"failed to read qemu-img version(%S), assuming 0.9")
> + line;
> + 0, 9
> + )
> +
> +let find_file_in_tar tar filename =
> + let lines = external_command (sprintf "tar tRvf %s" (Filename.quote tar)) in
> + let rec loop lines =
> + match lines with
> + | [] -> raise Not_found
> + | line :: lines -> (
> + (* Lines have the form:
> + * block <offset>: <perms> <owner>/<group> <size> <mdate> <mtime> <file>
> + *)
> + let elems = Str.bounded_split (Str.regexp " +") line 8 in
> + if List.length elems = 8 && List.hd elems = "block" then (
> + let elems = Array.of_list elems in
> + let offset = elems.(1) in
> + let size = elems.(4) in
> + let fname = elems.(7) in
> +
> + if fname <> filename then
> + loop lines
> + else (
> + let offset =
> + try
> + (* There should be a colon at the end *)
> + let i = String.rindex offset ':' in
> + if i == (String.length offset)-1 then
> + Int64.of_string (String.sub offset 0 i)
> + else
> + raise (Failure "colon at wrong position")
> + with Failure _ | Not_found ->
> + raise (Failure (sprintf "invalid offset returned by tar: %S"
> + offset)) in
> +
> + let size =
> + try Int64.of_string size
> + with Failure _ ->
> + raise (Failure (sprintf
> + "invalid size returned by tar: %S" size)) in
> +
> + (* Note: Offset is actualy block number and there is a single
> + * block with tar header at the beginning of the file. So skip
> + * the header and convert the block number to bytes before
> + * returning.
> + *)
> + (offset +^ 1L) *^ 512L, size
> + )
> + ) else
> + raise (Failure (sprintf
> + "failed to parse line returned by tar: %S" line))
> + )
> + in
> + loop lines
> diff --git a/v2v/utils.mli b/v2v/utils.mli
> index 5eacb4aec..a9272568e 100644
> --- a/v2v/utils.mli
> +++ b/v2v/utils.mli
> @@ -50,3 +50,15 @@ val du : string -> int64
>
> This can raise either [Failure] or [Invalid_argument] in case
> of errors. *)
> +
> +val qemu_img_version : unit -> int * int
> +(** Returns version of qemu-img as a tuple (major, minor).
> +
> + In case of error (0,9) is returned. *)
> +
> +val find_file_in_tar : string -> string -> int64 * int64
> +(** [find_file_in_tar tar filename] looks up file in [tar] archive and returns
> + a tuple containing which byte it starts and how long the file is.
> +
> + Function raises [Not_found] if there is no such file inside [tar] and
> + [Failure] if there is any error parsing the tar output. *)
> --
> 2.11.0
>
> _______________________________________________
> Libguestfs mailing list
> Libguestfs at redhat.com
> https://www.redhat.com/mailman/listinfo/libguestfs
--
Richard Jones, Virtualization Group, Red Hat http://people.redhat.com/~rjones
Read my programming and virtualization blog: http://rwmj.wordpress.com
libguestfs lets you edit virtual machines. Supports shell scripting,
bindings from many languages. http://libguestfs.org
More information about the Libguestfs
mailing list