diff --git a/v2v/Makefile.am b/v2v/Makefile.am
index 28f937985..af43aa9f1 100644
--- a/v2v/Makefile.am
+++ b/v2v/Makefile.am
@@ -327,6 +327,7 @@ TESTS = \
test-v2v-i-ova-gz.sh \
test-v2v-i-ova-invalid-manifest1.sh \
test-v2v-i-ova-invalid-manifest2.sh \
+ test-v2v-i-ova-snapshots.sh \
test-v2v-i-ova-subfolders.sh \
test-v2v-i-ova-tar.sh \
test-v2v-i-ova-two-disks.sh \
@@ -466,6 +467,10 @@ EXTRA_DIST += \
test-v2v-i-ova-gz.sh \
test-v2v-i-ova-invalid-manifest1.sh \
test-v2v-i-ova-invalid-manifest2.sh \
+ test-v2v-i-ova-snapshots.expected \
+ test-v2v-i-ova-snapshots.expected2 \
+ test-v2v-i-ova-snapshots.ovf \
+ test-v2v-i-ova-snapshots.sh \
test-v2v-i-ova-subfolders.expected \
test-v2v-i-ova-subfolders.expected2 \
test-v2v-i-ova-subfolders.ovf \
diff --git a/v2v/input_ova.ml b/v2v/input_ova.ml
index 15c5b9171..c4711f902 100644
--- a/v2v/input_ova.ml
+++ b/v2v/input_ova.ml
@@ -27,6 +27,49 @@ open Parse_ova
open Parse_ovf_from_ova
open Name_from_disk
+(* RHBZ#1570407: VMware-generated OVA files found in the wild can
+ * contain hrefs referencing snapshots. The href will be something
+ * like: but the actual disk will be a
+ * snapshot called something like "disk1.vmdk.000000000".
+ *)
+let re_snapshot = PCRE.compile "\\.(\\d+)$"
+
+let rec find_file_or_snapshot ova_t href manifest =
+ match resolve_href ova_t href with
+ | Some f -> f
+ | None ->
+ (* Find all files in the OVA called [.\d+] *)
+ let files = get_file_list ova_t in
+ let snapshots =
+ List.filter_map (
+ function
+ | LocalFile filename -> get_snapshot_if_matches href filename
+ | TarFile (_, filename) -> get_snapshot_if_matches href filename
+ ) files in
+ (* Pick highest. *)
+ let snapshots = List.sort (fun a b -> compare b a) snapshots in
+ match snapshots with
+ | [] -> error_missing_href href
+ | snapshot::_ ->
+ let href = sprintf "%s.%s" href snapshot in
+ match resolve_href ova_t href with
+ | None -> error_missing_href href
+ | Some f -> f
+
+(* If [filename] matches [.\d+] then return [Some snapshot]. *)
+and get_snapshot_if_matches href filename =
+ if PCRE.matches re_snapshot filename then (
+ let snapshot = PCRE.sub 1 in
+ if String.is_suffix filename (sprintf "%s.%s" href snapshot) then
+ Some snapshot
+ else
+ None
+ )
+ else None
+
+and error_missing_href href =
+ error (f_"-i ova: OVF references file ā%sā which was not found in the OVA archive") href
+
class input_ova ova = object
inherit input
@@ -79,11 +122,7 @@ class input_ova ova = object
(* Convert the disk hrefs into qemu URIs. *)
let qemu_uris = List.map (
fun { href; compressed } ->
- let file_ref =
- match resolve_href ova_t href with
- | Some f -> f
- | None ->
- error (f_"-i ova: OVF references file ā%sā which was not found in the OVA archive") href in
+ let file_ref = find_file_or_snapshot ova_t href manifest in
match compressed, file_ref with
| false, LocalFile filename ->
diff --git a/v2v/test-v2v-i-ova-snapshots.expected b/v2v/test-v2v-i-ova-snapshots.expected
new file mode 100644
index 000000000..97bce58ad
--- /dev/null
+++ b/v2v/test-v2v-i-ova-snapshots.expected
@@ -0,0 +1,21 @@
+Source guest information (--print-source option):
+
+ source name: 2K8R2EESP1_2_Medium
+hypervisor type: vmware
+ memory: 1073741824 (bytes)
+ nr vCPUs: 1
+ CPU vendor:
+ CPU model:
+ CPU topology:
+ CPU features:
+ firmware: uefi
+ display:
+ video:
+ sound:
+disks:
+ disk1.vmdk (vmdk) [scsi]
+removable media:
+ CD-ROM [ide] in slot 0
+NICs:
+ Bridge "PG-VLAN60" [e1000]
+
diff --git a/v2v/test-v2v-i-ova-snapshots.expected2 b/v2v/test-v2v-i-ova-snapshots.expected2
new file mode 100644
index 000000000..45be3cc46
--- /dev/null
+++ b/v2v/test-v2v-i-ova-snapshots.expected2
@@ -0,0 +1,21 @@
+Source guest information (--print-source option):
+
+ source name: 2K8R2EESP1_2_Medium
+hypervisor type: vmware
+ memory: 1073741824 (bytes)
+ nr vCPUs: 1
+ CPU vendor:
+ CPU model:
+ CPU topology:
+ CPU features:
+ firmware: uefi
+ display:
+ video:
+ sound:
+disks:
+ json:{ "file": { "driver": "raw", "offset": x, "size": 12288, "file": { "driver": "file", "filename": "test-snapshots.ova" } } } (vmdk) [scsi]
+removable media:
+ CD-ROM [ide] in slot 0
+NICs:
+ Bridge "PG-VLAN60" [e1000]
+
diff --git a/v2v/test-v2v-i-ova-snapshots.ovf b/v2v/test-v2v-i-ova-snapshots.ovf
new file mode 100644
index 000000000..5e7c0d054
--- /dev/null
+++ b/v2v/test-v2v-i-ova-snapshots.ovf
@@ -0,0 +1,138 @@
+
+
+
+
+
+
+ Virtual disk information
+
+
+
+ The list of logical networks
+
+ The PG-VLAN60 network
+
+
+
+ A virtual machine
+ 2K8R2EESP1_2_Medium
+
+ The kind of installed guest operating system
+ Microsoft Windows Server 2008 R2 (64-bit)
+
+
+ Virtual hardware requirements
+
+ Virtual Hardware Family
+ 0
+ 2K8R2EESP1_2_Medium
+ vmx-10
+
+ -
+ hertz * 10^6
+ Number of Virtual CPUs
+ 1 virtual CPU(s)
+ 1
+ 3
+ 1
+
+ -
+ byte * 2^20
+ Memory Size
+ 1024MB of memory
+ 2
+ 4
+ 1024
+
+ -
+ 0
+ SCSI Controller
+ SCSI controller 0
+ 3
+ lsilogicsas
+ 6
+
+
+ -
+ 1
+ IDE Controller
+ IDE 1
+ 4
+ 5
+
+ -
+ 0
+ IDE Controller
+ IDE 0
+ 5
+ 5
+
+ -
+ false
+ Video card
+ 6
+ 24
+
+
+
+
+
+ -
+ false
+ VMCI device
+ 7
+ vmware.vmci
+ 1
+
+
+
+ -
+ 0
+ false
+ CD/DVD drive 1
+ 8
+ 4
+ vmware.cdrom.atapi
+ 15
+
+ -
+ 0
+ Hard disk 1
+ ovf:/disk/vmdisk1
+ 9
+ 3
+ 17
+
+
+ -
+ 7
+ true
+ PG-VLAN60
+ E1000 ethernet adapter on "PG-VLAN60"
+ Network adapter 1
+ 11
+ E1000
+ 10
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/v2v/test-v2v-i-ova-snapshots.sh b/v2v/test-v2v-i-ova-snapshots.sh
new file mode 100755
index 000000000..7649d22f9
--- /dev/null
+++ b/v2v/test-v2v-i-ova-snapshots.sh
@@ -0,0 +1,78 @@
+#!/bin/bash -
+# libguestfs virt-v2v test script
+# Copyright (C) 2014-2018 Red Hat Inc.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+# Test -i ova option with OVA file containing snapshots.
+# https://bugzilla.redhat.com/show_bug.cgi?id=1570407
+
+unset CDPATH
+export LANG=C
+set -e
+
+$TEST_FUNCTIONS
+skip_if_skipped
+skip_if_backend uml
+
+export VIRT_TOOLS_DATA_DIR="$top_srcdir/test-data/fake-virt-tools"
+
+d=test-v2v-i-ova-snapshots.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.
+# In these weird OVAs, disk1.vmdk does not exist, but both the
+# href and manifest reference it. virt-v2v should use the
+# highest numbered snapshot instead.
+guestfish disk-create disk1.vmdk.000000000 raw 10k
+guestfish disk-create disk1.vmdk.000000001 raw 11k
+guestfish disk-create disk1.vmdk.000000002 raw 12k
+sha=`do_sha1 disk1.vmdk.000000002`
+echo -e "SHA1(disk1.vmdk)= $sha\r" > disk1.mf
+sha=`do_sha1 disk1.vmdk.000000000`
+echo -e "SHA1(disk1.vmdk.000000000)= $sha\r" > disk1.mf
+sha=`do_sha1 disk1.vmdk.000000001`
+echo -e "SHA1(disk1.vmdk.000000001)= $sha\r" > disk1.mf
+sha=`do_sha1 disk1.vmdk.000000002`
+echo -e "SHA1(disk1.vmdk.000000002)= $sha\r" > disk1.mf
+cp ../test-v2v-i-ova-snapshots.ovf .
+tar -cf test-snapshots.ova test-v2v-i-ova-snapshots.ovf disk1.vmdk.00000000? 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-snapshots.ova \
+ --print-source > $d/source
+
+# Check the parsed source is what we expect.
+if grep -sq json: $d/source ; then
+ # Normalize the output.
+ # Remove directory prefix.
+ # Exact offset will vary because of tar.
+ sed -i -e "s,\"[^\"]*/$d/,\"," \
+ -e "s|\"offset\": [0-9]*,|\"offset\": x,|" $d/source
+ diff -u test-v2v-i-ova-snapshots.expected2 $d/source
+else
+ # normalize the output
+ sed -i -e 's,[^ \t]*\(disk.*.vmdk\),\1,' $d/source
+ diff -u test-v2v-i-ova-snapshots.expected $d/source
+fi
+
+rm -rf $d