diff --git a/src/PVE/GuestImport/OVF.pm b/src/PVE/GuestImport/OVF.pm index a228cd2..f00c36a 100644 --- a/src/PVE/GuestImport/OVF.pm +++ b/src/PVE/GuestImport/OVF.pm @@ -385,7 +385,46 @@ ovf:Item[rasd:InstanceID='%s']/rasd:ResourceType", $controller_id, $net_count++; } - return { qm => $qm, disks => \@disks, net => $net }; + # CD drives are added empty, as images cannot be imported, but the referenced image is kept + # so the user can be told about it + my $cdrom_id = dtmf_name_to_id('CD Drive'); + my $xpath_find_cdroms = + "/ovf:Envelope/ovf:VirtualSystem/ovf:VirtualHardwareSection/ovf:Item[rasd:ResourceType=${cdrom_id}]"; + my @cdrom_items = $xpc->findnodes($xpath_find_cdroms); + + my $addresses_in_use = { map { $_->{disk_address} => 1 } @disks }; + my @cdroms; + for my $item_node (@cdrom_items) { + my $controller_id = $xpc->findvalue('rasd:Parent', $item_node); + my $xpath_find_parent_type = sprintf( + "/ovf:Envelope/ovf:VirtualSystem/ovf:VirtualHardwareSection/\ +ovf:Item[rasd:InstanceID='%s']/rasd:ResourceType", $controller_id, + ); + my $bus = id_to_pve($xpc->findvalue($xpath_find_parent_type)); + if (!$bus) { + warn "invalid or missing controller for CD drive, skipping\n"; + next; + } + + my $index = $xpc->findvalue('rasd:AddressOnParent', $item_node); + $index = 0 if $index !~ m/^\d+$/; + $index++ while $addresses_in_use->{"$bus$index"}; + my $address = "$bus$index"; + $addresses_in_use->{$address} = 1; + + my $image; + my $host_resource = $xpc->findvalue('rasd:HostResource', $item_node); + if ($host_resource =~ m|^(?:ovf:)?/file/([[:alnum:]\-\._~]+)$|) { + my $xpath_find_image = + sprintf("/ovf:Envelope/ovf:References/ovf:File[\@ovf:id='%s']/\@ovf:href", $1); + $image = $xpc->findvalue($xpath_find_image) || undef; + } + + $qm->{$address} = 'none,media=cdrom'; + push @cdroms, { disk_address => $address, image => $image }; + } + + return { qm => $qm, disks => \@disks, net => $net, cdroms => \@cdroms }; } my sub resolve_disks { @@ -436,6 +475,8 @@ my sub resolve_disks { push @$boot_order, $pve_disk_address; } + push @$boot_order, map { $_->{disk_address} } $qm->{cdroms}->@*; + $qm->{qm}->{boot} = "order=" . join(';', @$boot_order) if scalar(@$boot_order) > 0; $qm->{disks} = \@disks; diff --git a/src/PVE/Storage/DirPlugin.pm b/src/PVE/Storage/DirPlugin.pm index 80c4a03..723a59a 100644 --- a/src/PVE/Storage/DirPlugin.pm +++ b/src/PVE/Storage/DirPlugin.pm @@ -299,6 +299,16 @@ sub get_import_metadata { }; } + for my $cdrom ($res->{cdroms}->@*) { + next if !defined($cdrom->{image}); + push @$warnings, + { + type => 'cdrom-image-ignored', + key => $cdrom->{disk_address}, + value => $cdrom->{image}, + }; + } + if (defined($res->{qm}->{bios}) && $res->{qm}->{bios} eq 'ovmf') { $disks->{efidisk0} = 1; push @$warnings, { type => 'efi-state-lost', key => 'bios', value => 'ovmf' }; diff --git a/src/test/ovf_manifests/cdrom-image.ovf b/src/test/ovf_manifests/cdrom-image.ovf new file mode 100644 index 0000000..665461c --- /dev/null +++ b/src/test/ovf_manifests/cdrom-image.ovf @@ -0,0 +1,116 @@ + + + + + + + + + Virtual disk information + + + + + The list of logical networks + + Service Port + + + + A virtual machine + cdrom-image + + The kind of installed guest operating system + + + Virtual hardware requirements + + Virtual Hardware Family + 0 + cdrom-image + vmx-07 + + + hertz * 10^6 + Number of Virtual CPUs + 1 virtual CPU(s) + 1 + 3 + 1 + + + byte * 2^20 + Memory Size + 2048MB of memory + 2 + 4 + 2048 + + + 0 + SCSI Controller + SCSI Controller 0 + 3 + lsilogic + 6 + + + 1 + IDE Controller + VirtualIDEController 1 + 4 + 5 + + + 0 + IDE Controller + VirtualIDEController 0 + 5 + 5 + + + 0 + Hard Disk 1 + ovf:/disk/vmdisk1 + 6 + 3 + 17 + + + 0 + Hard Disk 2 + ovf:/disk/vmdisk2 + 7 + 5 + 17 + + + 0 + false + Floppy Drive + Floppy 1 + 8 + 14 + + + 0 + true + CD-ROM 1 + ovf:/file/file2 + 9 + 4 + 15 + + + 9 + true + VM Network + E1000 ethernet adapter on "VM Network" + Ethernet 0 + 10 + E1000 + 10 + + + + diff --git a/src/test/run_ovf_tests.pl b/src/test/run_ovf_tests.pl index 23f0aa6..1215882 100644 --- a/src/test/run_ovf_tests.pl +++ b/src/test/run_ovf_tests.pl @@ -45,6 +45,14 @@ if (my $err = $@) { fail('parsing XEE.ovf should have failed!'); } +my $cdromImage = eval { PVE::GuestImport::OVF::parse_ovf("$test_manifests/cdrom-image.ovf") }; +if (my $err = $@) { + fail('parse cdrom image'); + warn("error: $err\n"); +} else { + ok('parse cdrom image'); +} + print "testing disks\n"; is( @@ -101,22 +109,43 @@ is($win10->{net}->{net0}->{model}, 'e1000e', 'win10 has correct nic model'); is($win10noNs->{net}->{net0}->{model}, 'e1000e', 'win10 (no default rasd NS) has correct nic model'); +print "testing cdroms\n"; +is_deeply( + $win2008->{cdroms}, + [{ disk_address => 'sata1', image => undef }], + 'win2008 has an empty cdrom', +); +is($win2008->{qm}->{sata1}, 'none,media=cdrom', 'win2008 cdrom is added to the config'); +is_deeply( + $cdromImage->{cdroms}, + [{ disk_address => 'ide1', image => 'installer.iso' }], + 'cdrom with image gets the next free address on its bus', +); +is($cdromImage->{qm}->{ide1}, 'none,media=cdrom', 'cdrom with image is added empty to the config'); +is_deeply( + [map { $_->{disk_address} } $cdromImage->{disks}->@*], + ['scsi0', 'ide0'], + 'cdrom with image vm has the correct disks', +); + print "\ntesting vm.conf extraction\n"; -is($win2008->{qm}->{boot}, 'order=scsi0;scsi1', 'win2008 VM boot is correct'); +is($win2008->{qm}->{boot}, 'order=scsi0;scsi1;sata1', 'win2008 VM boot is correct'); is($win2008->{qm}->{name}, 'Win2008-R2x64', 'win2008 VM name is correct'); is($win2008->{qm}->{memory}, '2048', 'win2008 VM memory is correct'); is($win2008->{qm}->{cores}, '1', 'win2008 VM cores are correct'); is($win2008->{qm}->{ostype}, 'win7', 'win2008 VM ostype is correcty'); -is($win10->{qm}->{boot}, 'order=scsi0', 'win10 VM boot is correct'); +is($win10->{qm}->{boot}, 'order=scsi0;sata1', 'win10 VM boot is correct'); is($win10->{qm}->{name}, 'Win10-Liz', 'win10 VM name is correct'); is($win10->{qm}->{memory}, '6144', 'win10 VM memory is correct'); is($win10->{qm}->{cores}, '4', 'win10 VM cores are correct'); # older esxi/ovf standard used 'other' for windows10 is($win10->{qm}->{ostype}, 'other', 'win10 VM ostype is correct'); -is($win10noNs->{qm}->{boot}, 'order=scsi0', 'win10 VM (no default rasd NS) boot is correct'); +is( + $win10noNs->{qm}->{boot}, 'order=scsi0;sata1', 'win10 VM (no default rasd NS) boot is correct', +); is($win10noNs->{qm}->{name}, 'Win10-Liz', 'win10 VM (no default rasd NS) name is correct'); is($win10noNs->{qm}->{memory}, '6144', 'win10 VM (no default rasd NS) memory is correct'); is($win10noNs->{qm}->{cores}, '4', 'win10 VM (no default rasd NS) cores are correct'); @@ -124,4 +153,6 @@ is($win10noNs->{qm}->{cores}, '4', 'win10 VM (no default rasd NS) cores are corr is($win10noNs->{qm}->{ostype}, 'other', 'win10 VM (no default rasd NS) ostype is correct'); is($win10noNs->{qm}->{bios}, 'ovmf', 'win10 VM (no default rasd NS) bios is correct'); +is($cdromImage->{qm}->{boot}, 'order=scsi0;ide0;ide1', 'cdrom with image VM boot is correct'); + done_testing();