[pve-devel] [PATCH qemu-server v3 2/4] Initial parsing of OVF files

Emmanuel Kasper e.kasper at proxmox.com
Wed Mar 29 16:53:13 CEST 2017


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   | 220 ++++++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 221 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..bdca880
--- /dev/null
+++ b/PVE/QemuServer/OVF.pm
@@ -0,0 +1,220 @@
+# 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 File::Spec;
+use File::Basename;
+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
+    { 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 $@;
+
+    # 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
+    if (!($qm->{name} = $ovf_name)) {
+	warn "warning: unable to parse the VM name in this OVF manifest, generating a default value\n";
+    }
+
+    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'} =~ /^(?:\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'} =~ /\Q$disk_id\E/ } @virtual_hardware_items;
+
+    foreach my $item_node (@virtual_hardware_items) {
+	if ($item_node->{'rasd:ResourceType'} == dtmf_name_to_id('Processor')) {
+	    if (!($qm->{'cores'} = $item_node->{'rasd:VirtualQuantity'})) {
+		warn "warning: unable to count the number of cores in this OVF manifest\n";
+	    }
+	}
+	elsif ($item_node->{'rasd:ResourceType'} == dtmf_name_to_id('Memory')) {
+	    if (!($qm->{'memory'} = $item_node->{'rasd:VirtualQuantity'})) {
+		warn "warning: unable to parse memory in this OVF manifest n";
+	    }
+	}
+    }
+
+    # 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 =~ m|^ovf:/(.+)/(.+)|) {
+	    $disk_section_path = $1;
+	    $disk_id = $2;
+	} else {
+	   warn "invalid host ressource $host_resource, skipping\n";
+	   next;
+	}
+	print "disk section path and disk id: ", Dumper($disk_section_path, $disk_id) if $debug;
+	
+	#single disk VM
+	if (ref($tree->{DiskSection}->{Disk}) 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}->{Disk} }) {
+		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') {
+	    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 $backing_file = $file_node->{'ovf:href'};
+	my $backing_file_abs_path = join ('/', dirname(File::Spec->rel2abs($ovf)), $backing_file);
+
+	$pve_disk = {
+	    disk_address => $pve_disk_address,
+	    backing_file => $backing_file_abs_path
+	};
+	push @disks, $pve_disk;
+
+    }
+
+    return {qm => $qm, disks => \@disks};
+}
+
+1;
-- 
2.1.4





More information about the pve-devel mailing list