]>
git.proxmox.com Git - qemu-server.git/blob - PVE/QemuServer/OVF.pm
1 # Open Virtualization Format import routines
2 # https://www.dmtf.org/standards/ovf
3 package PVE
::QemuServer
::OVF
;
17 # map OVF resources types to descriptive strings
18 # this will allow us to explore the xml tree without using magic numbers
19 # http://schemas.dmtf.org/wbem/cim-html/2/CIM_ResourceAllocationSettingData.html
21 { id
=> 1, dtmf_name
=> 'Other' },
22 { id
=> 2, dtmf_name
=> 'Computer System' },
23 { id
=> 3, dtmf_name
=> 'Processor' },
24 { id
=> 4, dtmf_name
=> 'Memory' },
25 { id
=> 5, dtmf_name
=> 'IDE Controller', pve_type
=> 'ide' },
26 { id
=> 6, dtmf_name
=> 'Parallel SCSI HBA', pve_type
=> 'scsi' },
27 { id
=> 7, dtmf_name
=> 'FC HBA' },
28 { id
=> 8, dtmf_name
=> 'iSCSI HBA' },
29 { id
=> 9, dtmf_name
=> 'IB HCA' },
30 { id
=> 10, dtmf_name
=> 'Ethernet Adapter' },
31 { id
=> 11, dtmf_name
=> 'Other Network Adapter' },
32 { id
=> 12, dtmf_name
=> 'I/O Slot' },
33 { id
=> 13, dtmf_name
=> 'I/O Device' },
34 { id
=> 14, dtmf_name
=> 'Floppy Drive' },
35 { id
=> 15, dtmf_name
=> 'CD Drive' },
36 { id
=> 16, dtmf_name
=> 'DVD drive' },
37 { id
=> 17, dtmf_name
=> 'Disk Drive' },
38 { id
=> 18, dtmf_name
=> 'Tape Drive' },
39 { id
=> 19, dtmf_name
=> 'Storage Extent' },
40 { id
=> 20, dtmf_name
=> 'Other storage device', pve_type
=> 'sata'},
41 { id
=> 21, dtmf_name
=> 'Serial port' },
42 { id
=> 22, dtmf_name
=> 'Parallel port' },
43 { id
=> 23, dtmf_name
=> 'USB Controller' },
44 { id
=> 24, dtmf_name
=> 'Graphics controller' },
45 { id
=> 25, dtmf_name
=> 'IEEE 1394 Controller' },
46 { id
=> 26, dtmf_name
=> 'Partitionable Unit' },
47 { id
=> 27, dtmf_name
=> 'Base Partitionable Unit' },
48 { id
=> 28, dtmf_name
=> 'Power' },
49 { id
=> 29, dtmf_name
=> 'Cooling Capacity' },
50 { id
=> 30, dtmf_name
=> 'Ethernet Switch Port' },
51 { id
=> 31, dtmf_name
=> 'Logical Disk' },
52 { id
=> 32, dtmf_name
=> 'Storage Volume' },
53 { id
=> 33, dtmf_name
=> 'Ethernet Connection' },
54 { id
=> 34, dtmf_name
=> 'DMTF reserved' },
55 { id
=> 35, dtmf_name
=> 'Vendor Reserved'}
59 my ($key, $param) = @_;
60 foreach my $resource (@resources) {
61 if ($resource->{$key} eq $param) {
70 my $found = find_by
('dtmf_name', $dtmf_name);
80 my $resource = find_by
('id', $id);
82 return $resource->{pve_type
};
88 # returns two references, $qm which holds qm.conf style key/values, and \@disks
90 my ($ovf, $debug) = @_;
92 my $dom = XML
::LibXML-
>load_xml(location
=> $ovf, no_blanks
=> 1);
94 # register the xml namespaces in a xpath context object
95 # 'ovf' is the default namespace so it will prepended to each xml element
96 my $xpc = XML
::LibXML
::XPathContext-
>new($dom);
97 $xpc->registerNs('ovf', 'http://schemas.dmtf.org/ovf/envelope/1');
98 $xpc->registerNs('rasd', 'http://schemas.dmtf.org/wbem/wscim/1/cim-schema/2/CIM_ResourceAllocationSettingData');
99 $xpc->registerNs('vssd', 'http://schemas.dmtf.org/wbem/wscim/1/cim-schema/2/CIM_VirtualSystemSettingData');
102 # hash to save qm.conf parameters
105 #array to save a disk list
109 # walk down the dom until we find the matching XML element
110 my $xpath_find_name = "/ovf:Envelope/ovf:VirtualSystem/ovf:Name";
111 my $ovf_name = $xpc->findvalue($xpath_find_name);
114 # PVE::QemuServer::confdesc requires a valid DNS name
115 ($qm->{name
} = $ovf_name) =~ s/[^a-zA-Z0-9\-\.]//g;
117 warn "warning: unable to parse the VM name in this OVF manifest, generating a default value\n";
121 # element[child] search the elements which have this [child]
122 my $processor_id = dtmf_name_to_id
('Processor');
123 my $xpath_find_vcpu_count = "/ovf:Envelope/ovf:VirtualSystem/ovf:VirtualHardwareSection/ovf:Item[rasd:ResourceType=${processor_id}]/rasd:VirtualQuantity";
124 $qm->{'cores'} = $xpc->findvalue($xpath_find_vcpu_count);
126 my $memory_id = dtmf_name_to_id
('Memory');
127 my $xpath_find_memory = ("/ovf:Envelope/ovf:VirtualSystem/ovf:VirtualHardwareSection/ovf:Item[rasd:ResourceType=${memory_id}]/rasd:VirtualQuantity");
128 $qm->{'memory'} = $xpc->findvalue($xpath_find_memory);
131 # here we expect multiple results, so we do not read the element value with
132 # findvalue() but store multiple elements with findnodes()
133 my $disk_id = dtmf_name_to_id
('Disk Drive');
134 my $xpath_find_disks="/ovf:Envelope/ovf:VirtualSystem/ovf:VirtualHardwareSection/ovf:Item[rasd:ResourceType=${disk_id}]";
135 my @disk_items = $xpc->findnodes($xpath_find_disks);
137 # disks metadata is split in four different xml elements:
138 # * as an Item node of type DiskDrive in the VirtualHardwareSection
139 # * as an Disk node in the DiskSection
140 # * as a File node in the References section
141 # * each Item node also holds a reference to its owning controller
143 # we iterate over the list of Item nodes of type disk drive, and for each item,
144 # find the corresponding Disk node, and File node and owning controller
145 # when all the nodes has been found out, we copy the relevant information to
146 # a $pve_disk hash ref, which we push to @disks;
148 foreach my $item_node (@disk_items) {
155 print "disk item:\n", $item_node->toString(1), "\n" if $debug;
157 # from Item, find corresponding Disk node
158 # here the dot means the search should start from the current element in dom
159 my $host_resource = $xpc->findvalue('rasd:HostResource', $item_node);
160 my $disk_section_path;
163 # RFC 3986 "2.3. Unreserved Characters"
164 my $valid_uripath_chars = qr/[[:alnum:]]|[\-\._~]/;
166 if ($host_resource =~ m
|^ovf
:/(${valid_uripath_chars}+)/(${valid_uripath_chars
}+)$|) {
167 $disk_section_path = $1;
170 warn "invalid host ressource $host_resource, skipping\n";
173 printf "disk section path: $disk_section_path and disk id: $disk_id\n" if $debug;
176 # @ means we filter the result query based on a the value of an item attribute ( @ = attribute)
177 # @ needs to be escaped to prevent Perl double quote interpolation
178 my $xpath_find_fileref = sprintf("/ovf:Envelope/ovf:DiskSection/\
179 ovf:Disk[\@ovf:diskId='%s']/\@ovf:fileRef", $disk_id);
180 my $fileref = $xpc->findvalue($xpath_find_fileref);
182 my $valid_url_chars = qr
@${valid_uripath_chars
}|/@;
183 if (!$fileref || $fileref !~ m/^${valid_url_chars}+$/) {
184 warn "invalid host ressource $host_resource, skipping\n";
188 # from Disk Node, find corresponding filepath
189 my $xpath_find_filepath = sprintf("/ovf:Envelope/ovf:References/ovf:File[\@ovf:id='%s']/\@ovf:href", $fileref);
190 my $filepath = $xpc->findvalue($xpath_find_filepath);
192 warn "invalid file reference $fileref, skipping\n";
195 print "file path: $filepath\n" if $debug;
197 # from Item, find owning Controller type
198 my $controller_id = $xpc->findvalue('rasd:Parent', $item_node);
199 my $xpath_find_parent_type = sprintf("/ovf:Envelope/ovf:VirtualSystem/ovf:VirtualHardwareSection/\
200 ovf:Item[rasd:InstanceID='%s']/rasd:ResourceType", $controller_id);
201 my $controller_type = $xpc->findvalue($xpath_find_parent_type);
202 if (!$controller_type) {
203 warn "invalid or missing controller: $controller_type, skipping\n";
206 print "owning controller type: $controller_type\n" if $debug;
208 # extract corresponding Controller node details
209 my $adress_on_controller = $xpc->findvalue('rasd:AddressOnParent', $item_node);
210 my $pve_disk_address = id_to_pve
($controller_type) . $adress_on_controller;
212 # resolve symlinks and relative path components
213 # and die if the diskimage is not somewhere under the $ovf path
214 my $ovf_dir = realpath
(dirname
(File
::Spec-
>rel2abs($ovf)));
215 my $backing_file_path = realpath
(join ('/', $ovf_dir, $filepath));
216 if ($backing_file_path !~ /^\Q${ovf_dir}\E/) {
217 die "error parsing $filepath, are you using a symlink ?\n";
220 if (!-e
$backing_file_path) {
221 die "error parsing $filepath, file seems not to exist at $backing_file_path\n";
224 ($backing_file_path) = $backing_file_path =~ m
|^(/.*)|; # untaint
226 my $virtual_size = PVE
::Storage
::file_size_info
($backing_file_path);
227 die "error parsing $backing_file_path, cannot determine file size\n"
231 disk_address
=> $pve_disk_address,
232 backing_file
=> $backing_file_path,
233 virtual_size
=> $virtual_size
235 push @disks, $pve_disk;
239 return {qm
=> $qm, disks
=> \
@disks};