Also adds a regression test. --- v2v/Makefile.am | 5 ++ v2v/input_ova.ml | 49 ++++++++++-- v2v/test-v2v-i-ova-snapshots.expected | 21 +++++ v2v/test-v2v-i-ova-snapshots.expected2 | 21 +++++ v2v/test-v2v-i-ova-snapshots.ovf | 138 +++++++++++++++++++++++++++++++++ v2v/test-v2v-i-ova-snapshots.sh | 78 +++++++++++++++++++ 6 files changed, 307 insertions(+), 5 deletions(-) diff --git a/v2v/Makefile.am b/v2v/Makefile.am index c9ed1fc88..9d9ab8d50 100644 --- a/v2v/Makefile.am +++ b/v2v/Makefile.am @@ -329,6 +329,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 \ @@ -474,6 +475,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 9f7bc64ff..5953f3401 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: <File href="disk1.vmdk"/> 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 get_file_ref ova_t href with + | Some f -> f + | None -> + (* Find all files in the manifest called [<href>.\d+] *) + let manifest = List.map fst manifest (* just the file_ref's *) in + let snapshots = + List.filter_map ( + function + | LocalFile filename -> get_snapshot_if_matches href filename + | TarFile (_, filename) -> get_snapshot_if_matches href filename + ) manifest 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 get_file_ref ova_t href with + | None -> error_missing_href href + | Some f -> f + +(* If [filename] matches [<href>.\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 get_file_ref 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 @@ +<?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_ResourceAllocationSett..." xmlns:vmw="http://www.vmware.com/schema/ovf" xmlns:vssd="http://schemas.dmtf.org/wbem/wscim/1/cim-schema/2/CIM_VirtualSystemSettingDa..." xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> + <References> + <File ovf:href="disk1.vmdk" ovf:id="file1" ovf:size="12288"/> + </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-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 -- 2.16.2