[pve-devel] [RFC PATCH qemu-server v2 3/5] Initial parsing of OVF files
Emmanuel Kasper
e.kasper at proxmox.com
Tue Feb 28 09:13:44 CET 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 | 217 ++++++++++++++++++++++++++++++++++++++++++++++++
2 files changed, 218 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..0f63891
--- /dev/null
+++ b/PVE/QemuServer/OVF.pm
@@ -0,0 +1,217 @@
+# 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'} =~ /$scsi_id|$ide_id|$sata_id/ } @virtual_hardware_items;
+
+ my $disk_id = dtmf_name_to_id('Disk Drive');
+ my @disk_items = grep { $_->{'rasd:ResourceType'} =~ /$disk_id/ } @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 =~ 'ovf:/(.*)/(.*)') {
+ $disk_section_path = $1;
+ $disk_id = $2;
+ }
+ 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