[pve-devel] [PATCH qemu-server v4 3/5] Initial support for importing OVF virtual machines

Wolfgang Bumiller w.bumiller at proxmox.com
Thu Apr 27 12:20:13 CEST 2017


On Thu, Mar 30, 2017 at 04:51:23PM +0200, 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   | 223 ++++++++++++++++++++++++++++++++++++++++++++++++
>  2 files changed, 224 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..5edf684
> --- /dev/null
> +++ b/PVE/QemuServer/OVF.pm
> @@ -0,0 +1,223 @@
> +# Open Virtualization Format import routines
> +# https://www.dmtf.org/standards/ovf
> +package PVE::QemuServer::OVF;
> +
> +use strict;
> +use warnings;
> +
> +use XML::LibXML;
> +use File::Spec;
> +use File::Basename;
> +use Data::Dumper;
> +
> +use PVE::Tools;
> +use PVE::Storage::Plugin;
> +
> +# map OVF resources 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'},
> +    { 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 $dom = XML::LibXML->load_xml(location => $ovf, no_blanks => 1);
> +
> +    # register the xml namespaces in a xpath context object
> +    # 'ovf' is the default namespace so it will prepended to each xml element
> +    my $xpc = XML::LibXML::XPathContext->new($dom);
> +    $xpc->registerNs('ovf', 'http://schemas.dmtf.org/ovf/envelope/1');
> +    $xpc->registerNs('rasd', 'http://schemas.dmtf.org/wbem/wscim/1/cim-schema/2/CIM_ResourceAllocationSettingData');
> +    $xpc->registerNs('vssd', 'http://schemas.dmtf.org/wbem/wscim/1/cim-schema/2/CIM_VirtualSystemSettingData');
> +
> +
> +    # hash to save qm.conf parameters
> +    my $qm;
> +
> +    #array to save a disk list
> +    my @disks;
> +
> +    # easy xpath
> +    # walk down the dom until we find the matching XML element
> +    my $xpath_find_name = "/ovf:Envelope/ovf:VirtualSystem/ovf:Name";
> +    my $ovf_name = $xpc->findvalue($xpath_find_name);
> +
> +    if ($ovf_name) {
> +	($qm->{name} = $ovf_name) =~ s/[^a-zA-Z0-9\-]//g; # PVE::QemuServer::confdesc requires a valid DNS name
> +    } else {
> +	warn "warning: unable to parse the VM name in this OVF manifest, generating a default value\n";
> +    }
> +
> +    # middle level xpath
> +    # element[child] search the elements which have this [child]
> +    my $processor_id = dtmf_name_to_id('Processor');
> +    my $xpath_find_vcpu_count = "/ovf:Envelope/ovf:VirtualSystem/ovf:VirtualHardwareSection/ovf:Item[rasd:ResourceType=${processor_id}]/rasd:VirtualQuantity";
> +    $qm->{'cores'} = $xpc->findvalue($xpath_find_vcpu_count);
> +
> +    my $memory_id = dtmf_name_to_id('Memory');
> +    my $xpath_find_memory = ("/ovf:Envelope/ovf:VirtualSystem/ovf:VirtualHardwareSection/ovf:Item[rasd:ResourceType=${memory_id}]/rasd:VirtualQuantity");
> +    $qm->{'memory'} = $xpc->findvalue($xpath_find_memory);
> +
> +    # middle level xpath
> +    # here we expect multiple results, so we do not read the element value with
> +    # findvalue() but store multiple elements with findnodes()
> +    my $disk_id = dtmf_name_to_id('Disk Drive');
> +    my $xpath_find_disks="/ovf:Envelope/ovf:VirtualSystem/ovf:VirtualHardwareSection/ovf:Item[rasd:ResourceType=${disk_id}]";
> +    my @disk_items = $xpc->findnodes($xpath_find_disks);
> +
> +    # disks metadata is split in four different xml elements:
> +    # * 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 copy the relevant information to
> +    # a $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;
> +
> +	print "disk item:\n", $item_node->toString(1), "\n" if $debug;
> +
> +	# from Item, find corresponding Disk node
> +	# here the dot means the search should start from the current element in dom
> +	my $host_resource = $item_node->findvalue('./rasd:HostResource');
> +	my $disk_section_path;
> +	my $disk_id;
> +	if ($host_resource =~ m|^ovf:/(.+)/(.+)|) {

Can these IDs be anything? How is this supposed to treat a string with
more than 2 slashes, or ones with special chars (see the next note
below).

> +	    $disk_section_path = $1;
> +	    $disk_id = $2;
> +	} else {
> +	   warn "invalid host ressource $host_resource, skipping\n";
> +	   next;
> +	}
> +	printf "disk section path: $disk_section_path and disk id: $disk_id\n" if $debug;
> +
> +	# tricky xpath
> +	# @ means we filter the result query based on a the value of an item attribute ( @ = attribute)
> +	# @ needs to be escaped to prevent Perl double quote interpolation
> +	my $xpath_find_fileref = sprintf("/ovf:Envelope/ovf:DiskSection/\
> +ovf:Disk[\@ovf:diskId='%s']/\@ovf:fileRef", $disk_id);

Is there a particular reason for using sprintf instead of an
interpolated string (same for the rest below)? And what if $disk_id
contains quotes?

> +	my $fileref = $xpc->findvalue($xpath_find_fileref);
> +	if (!$fileref) {
> +	    warn "invalid host ressource $host_resource, skipping\n";
> +	    next;
> +	}
> +
> +	# from Disk Node, find corresponding filepath
> +	my $xpath_find_filepath = sprintf("/ovf:Envelope/ovf:References/ovf:File[\@ovf:id='%s']/\@ovf:href", $fileref);

Should we verify $fileref has no single quotes (or ends with a
backslash in case that matters)?

> +	my $filepath = $xpc->findvalue($xpath_find_filepath);
> +	if (!$filepath) {
> +	    warn "invalid file reference $fileref, skipping\n";
> +	    next;
> +	}
> +	print "file path: $filepath\n" if $debug;
> +
> +	# from Item, find owning Controller type
> +	my $controller_id = $item_node->findvalue('./rasd:Parent');
> +	my $xpath_find_parent_type = sprintf("/ovf:Envelope/ovf:VirtualSystem/ovf:VirtualHardwareSection/\
> +ovf:Item[rasd:InstanceID='%s']/rasd:ResourceType", $controller_id);
> +	my $controller_type = $xpc->findvalue($xpath_find_parent_type);
> +	if (!$controller_type) {
> +	    warn "invalid or missing controller: $controller_type, skipping\n";
> +	    next;
> +	}
> +	print "owning controller type: $controller_type\n" if $debug;
> +
> +	# extract corresponding Controller node details
> +	my $adress_on_controller = $item_node->findvalue('./rasd:AddressOnParent');
> +	my $pve_disk_address = id_to_pve($controller_type) . $adress_on_controller;
> +
> +	my $backing_file_abs_path = join ('/', dirname(File::Spec->rel2abs($ovf)), $filepath);

I'd say we need to verify the path actually points to something inside
the dirname(...($ovf))'s path here, to deal with `/../` paths pointing
to eg /dev/sda and symlinks.

> +
> +	my $virtual_size;
> +	if ( !($virtual_size = PVE::Storage::Plugin::file_size_info($backing_file_abs_path)) ) {
> +	    die "error parsing $backing_file_abs_path, size seems to be $virtual_size";
> +	}
> +
> +	$pve_disk = {
> +	    disk_address => $pve_disk_address,
> +	    backing_file => $backing_file_abs_path,
> +	    virtual_size => $virtual_size
> +	};
> +	push @disks, $pve_disk;
> +
> +    }
> +
> +    return {qm => $qm, disks => \@disks};
> +}
> +
> +1;
> -- 
> 2.1.4
> 
> 
> _______________________________________________
> pve-devel mailing list
> pve-devel at pve.proxmox.com
> http://pve.proxmox.com/cgi-bin/mailman/listinfo/pve-devel




More information about the pve-devel mailing list