[pve-devel] [RFC PATCH qemu-server 3/4] Initial parsing of OVF files
Wolfgang Bumiller
w.bumiller at proxmox.com
Thu Mar 23 11:33:33 CET 2017
On Tue, Feb 21, 2017 at 10:18:55AM +0100, Emmanuel Kasper wrote:
> Following OVF parameters will be extracted:
> * VM name
> * Memory
> * Number of cores
> * disks and their associated controllers
> ---
> PVE/QemuServer/Makefile | 1 +
> PVE/QemuServer/OVF.pm | 209 ++++++++++++++++++++++++++++++++++++++++++++++++
> 2 files changed, 210 insertions(+)
> create mode 100644 PVE/QemuServer/OVF.pm
>
> diff --git a/PVE/QemuServer/Makefile b/PVE/QemuServer/Makefile
> index 06617c5..46eaf98 100644
> --- a/PVE/QemuServer/Makefile
> +++ b/PVE/QemuServer/Makefile
> @@ -3,3 +3,4 @@ install:
> install -D -m 0644 PCI.pm ${DESTDIR}${PERLDIR}/PVE/QemuServer/PCI.pm
> install -D -m 0644 USB.pm ${DESTDIR}${PERLDIR}/PVE/QemuServer/USB.pm
> install -D -m 0644 Memory.pm ${DESTDIR}${PERLDIR}/PVE/QemuServer/Memory.pm
> + install -D -m 0644 OVF.pm ${DESTDIR}${PERLDIR}/PVE/QemuServer/OVF.pm
> diff --git a/PVE/QemuServer/OVF.pm b/PVE/QemuServer/OVF.pm
> new file mode 100644
> index 0000000..64893e7
> --- /dev/null
> +++ b/PVE/QemuServer/OVF.pm
> @@ -0,0 +1,209 @@
> +# Open Virtualization Format import routines
> +# https://www.dmtf.org/standards/ovf
> +package PVE::QemuServer::OVF;
> +
> +use strict;
> +use warnings;
> +
> +use XML::Simple;
> +use PVE::Tools;
> +use Data::Dumper;
> +
> +# map OVF resource types to descriptive strings
> +# this will allow us to explore the xml tree without using magic numbers
> +# http://schemas.dmtf.org/wbem/cim-html/2/CIM_ResourceAllocationSettingData.html
> +my @resources = (
> + { id => 1, dtmf_name => 'Other' },
> + { id => 2, dtmf_name => 'Computer System' },
> + { id => 3, dtmf_name => 'Processor' },
> + { id => 4, dtmf_name => 'Memory' },
> + { id => 5, dtmf_name => 'IDE Controller', pve_type => 'ide' },
> + { id => 6, dtmf_name => 'Parallel SCSI HBA', pve_type => 'scsi' },
> + { id => 7, dtmf_name => 'FC HBA' },
> + { id => 8, dtmf_name => 'iSCSI HBA' },
> + { id => 9, dtmf_name => 'IB HCA' },
> + { id => 10, dtmf_name => 'Ethernet Adapter' },
> + { id => 11, dtmf_name => 'Other Network Adapter' },
> + { id => 12, dtmf_name => 'I/O Slot' },
> + { id => 13, dtmf_name => 'I/O Device' },
> + { id => 14, dtmf_name => 'Floppy Drive' },
> + { id => 15, dtmf_name => 'CD Drive' },
> + { id => 16, dtmf_name => 'DVD drive' },
> + { id => 17, dtmf_name => 'Disk Drive' },
> + { id => 18, dtmf_name => 'Tape Drive' },
> + { id => 19, dtmf_name => 'Storage Extent' },
> + { id => 20, dtmf_name => 'Other storage device', pve_type => 'sata' }, # yes
yes?
> + { id => 21, dtmf_name => 'Serial port' },
> + { id => 22, dtmf_name => 'Parallel port' },
> + { id => 23, dtmf_name => 'USB Controller' },
> + { id => 24, dtmf_name => 'Graphics controller' },
> + { id => 25, dtmf_name => 'IEEE 1394 Controller' },
> + { id => 26, dtmf_name => 'Partitionable Unit' },
> + { id => 27, dtmf_name => 'Base Partitionable Unit' },
> + { id => 28, dtmf_name => 'Power' },
> + { id => 29, dtmf_name => 'Cooling Capacity' },
> + { id => 30, dtmf_name => 'Ethernet Switch Port' },
> + { id => 31, dtmf_name => 'Logical Disk' },
> + { id => 32, dtmf_name => 'Storage Volume' },
> + { id => 33, dtmf_name => 'Ethernet Connection' },
> + { id => 34, dtmf_name => 'DMTF reserved' },
> + { id => 35, dtmf_name => 'Vendor Reserved'}
> +);
> +
> +sub find_by {
> + my ($key, $param) = @_;
> + foreach my $resource (@resources) {
> + if ($resource->{$key} eq $param) {
> + return ($resource);
> + }
> + }
> + return undef;
> +}
> +
> +sub dtmf_name_to_id {
> + my ($dtmf_name) = @_;
> + my $found = find_by('dtmf_name', $dtmf_name);
> + if ($found) {
> + return $found->{id};
> + } else {
> + return undef;
> + }
> +}
> +
> +sub id_to_pve {
> + my ($id) = @_;
> + my $resource = find_by('id', $id);
> + if ($resource) {
> + return $resource->{pve_type};
> + } else {
> + return undef;
> + }
> +}
> +
> +# returns two references, $qm which holds qm.conf style key/values, and \@disks
> +sub parse_ovf {
> + my ($ovf, $debug) = @_;
> +
> + my $parser = XML::Simple->new();
> + my $xml = PVE::Tools::file_get_contents($ovf);
> + my $tree;
> + eval { $tree = $parser->XMLin($xml) };
> + die $@ if $@;
Seems like you can just leave out the eval block and do the assignment
right away.
> +
> + # hash to save qm.conf parameters
> + my $qm;
> +
> + #array to save a disk list
> + my @disks;
> +
> + my $ovf_name = $tree->{VirtualSystem}->{Name};
> + $ovf_name =~ s/[^a-zA-Z0-9\-]//g; # PVE::QemuServer::confdesc requires a valid DNS name
> + $qm->{name} = $ovf_name;
> +
> + my $scsi_id = dtmf_name_to_id('Parallel SCSI HBA');
> + my $ide_id = dtmf_name_to_id('IDE Controller');
> + my $sata_id = dtmf_name_to_id('Other storage device');
> +
> + my @virtual_hardware_items = @{ $tree->{VirtualSystem}->{VirtualHardwareSection}->{Item} };
> + my @controllers_items = grep { $_->{'rasd:ResourceType'} =~ /$scsi_id|$ide_id|$sata_id/ } @virtual_hardware_items;
The variables will be interpreted as REs there, you need to enclose them
in \Q \E pairs
/\Q$scsi_id\E|\Q$ide_id\E|\Q$sata_id\E/ } @virtual_hardware_items;
And you probably want to anchor the expression to match the entire
string:
/^(?:\Q$scsi_id\E|\Q$ide_id\E|\Q$sata_id\E)$/ } @virtual_hardware_items;
> +
> + my $disk_id = dtmf_name_to_id('Disk Drive');
> + my @disk_items = grep { $_->{'rasd:ResourceType'} =~ /$disk_id/ } @virtual_hardware_items;
same as above
> +
> + foreach my $item_node (@virtual_hardware_items) {
> + if ($item_node->{'rasd:ResourceType'} == dtmf_name_to_id('Processor')) {
> + $qm->{'cores'} = $item_node->{'rasd:VirtualQuantity'};
> + }
> + elsif ($item_node->{'rasd:ResourceType'} == dtmf_name_to_id('Memory')) {
> + $qm->{'memory'} = $item_node->{'rasd:VirtualQuantity'};
> + }
> + }
> +
> + # disks metadata is split in four different xml nodes:
> + # * as an Item node of type DiskDrive in the VirtualHardwareSection
> + # * as an Disk node in the DiskSection
> + # * as a File node in the References section
> + # * each Item node also holds a reference to its owning controller
> + #
> + # we iterate over the list of Item nodes of type disk drive, and for each item,
> + # find the corresponding Disk node, and File node and owning controller
> + # when all the nodes has been found out, we extract the relevant information to
> + # the $pve_disk hash ref, which we push to @disks;
> +
> + foreach my $item_node (@disk_items) {
> +
> + my $disk_node;
> + my $file_node;
> + my $controller_node;
> + my $pve_disk;
> +
> + # from Item, find corresponding Disk node
> + my $host_resource = $item_node->{'rasd:HostResource'};
> + my $disk_section_path;
> + my $disk_id;
> + if ($host_resource =~ 'ovf:/(.*)/(.*)') {
I'd prefer
... =~ m at ovf:/(.*)/(.*)@
And you probably want to anchor this as well (at least on the left side)
... =~ m@^ovf:/(.*)/(.*)@
> + $disk_section_path = $1;
> + $disk_id = $2;
> + }
Since you'll be using $disk_id later on you should error out here in an
else branch.
> + print "disk section path and disk id: ", Dumper($disk_section_path, $disk_id) if $debug;
> +
> + #single disk VM
> + if (ref($tree->{DiskSection}) eq 'HASH') {
> + if ($tree->{DiskSection}->{Disk}->{'ovf:diskId'} eq $disk_id) {
> + $disk_node = $tree->{DiskSection}->{'Disk'};
> + }
> + } else { # multiple disks VM
> + foreach my $entry (@{ $tree->{DiskSection} }) {
> + if ($entry->{'ovf:diskId'} eq $disk_id) {
> + $disk_node = $entry;
> + last;
> + }
> + }
> + }
> + print "disk node: ", Dumper($disk_node) if $debug;
> +
> + # from Disk Node, find corresponing File node
> + # single disk VM
> + if (ref($tree->{References}->{File}) eq 'HASH') {
If this value is not a hash or does not exist you might get warnings
about undefined values here.
my $file = $tree->{References}->{File};
if (ref($file) && ref($file) eq 'HASH') {
Note that since the same happens in the else branch. So apparently it
should be a hash or an array => check ref() for 'ARRAY' as an elsif
branch, then error/warn in a final else branch maybe?
> + my $entry = $tree->{References}->{File};
> + if ($entry->{'ovf:id'} eq $disk_node->{'ovf:fileRef'}) {
> + $file_node = $entry;
> + }
> + }
> + else { # multiple disks,
> + foreach my $entry (@{ $tree->{References}->{File} }) {
> + if ($entry->{'ovf:id'} eq $disk_node->{'ovf:fileRef'}) {
> + $file_node = $entry;
> + }
> + }
> + }
> + print "file node: ", Dumper($file_node) if $debug;
> +
> + # from Item, find corresponding Controller node
> + foreach my $controller (@controllers_items) {
> + if ($controller->{'rasd:InstanceID'} eq $item_node->{'rasd:Parent'}) {
> + $controller_node = $controller;
> + }
> + }
> + print "controller node: ", Dumper($controller_node) if $debug;
> +
> + # extract corresponding Controller node details
> + my $controller_id = $controller_node->{'rasd:ResourceType'};
> + my $adress_on_controller = $item_node->{'rasd:AddressOnParent'};
> +
> + my $pve_disk_address = id_to_pve($controller_id) . $adress_on_controller;
> +
> + my $pve_backing_file = $file_node->{'ovf:href'};
> +
> + $pve_disk = {
> + disk_address => $pve_disk_address,
> + backing_file => $pve_backing_file
> + };
> + push @disks, $pve_disk;
> +
> + }
> +
> + return {qm => $qm, disks => \@disks};
> +}
> +
> +1;
> --
> 2.1.4
More information about the pve-devel
mailing list