[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