v2v: -i ova: Factor out the OVF parsing into a separate module.

Mixing the XML parsing with the other functions of this module made it
very hard to understand.  Splitting the XML parsing into another
module simplifies the flow considerably.

This is just code refactoring and should not affect the semantics.
This commit is contained in:
Richard W.M. Jones
2017-03-12 19:47:38 +00:00
parent 57949e6853
commit f34636700a
4 changed files with 340 additions and 241 deletions

View File

@@ -53,6 +53,7 @@ SOURCES_MLI = \
output_rhv.mli \
output_vdsm.mli \
OVF.mli \
parse_ovf_from_ova.mli \
parse_libvirt_xml.mli \
qemu_command.mli \
target_bus_assignment.mli \
@@ -73,6 +74,7 @@ SOURCES_ML = \
DOM.ml \
changeuid.ml \
OVF.ml \
parse_ovf_from_ova.ml \
linux.ml \
windows.ml \
windows_virtio.ml \

View File

@@ -24,7 +24,7 @@ open Unix_utils
open Types
open Utils
open Xpath_helpers
open Parse_ovf_from_ova
open Name_from_disk
(* Return true if [libvirt] supports ["json:"] pseudo-URLs and accepts the
@@ -211,262 +211,97 @@ object
disk mf mode disk actual mode disk expected;
)
else
warning (f_"unable to parse line from manifest file: %S") line
;
warning (f_"unable to parse line from manifest file: %S") line;
loop ()
in
(try loop () with End_of_file -> ());
close_in chan
) mf;
(* Parse the ovf file. *)
let ovf_folder = Filename.dirname ovf in
let xml = read_whole_file ovf in
let doc = Xml.parse_memory xml in
(* Handle namespaces. *)
let xpathctx = Xml.xpath_new_context doc in
Xml.xpath_register_ns xpathctx
"ovf" "http://schemas.dmtf.org/ovf/envelope/1";
Xml.xpath_register_ns xpathctx
"rasd" "http://schemas.dmtf.org/wbem/wscim/1/cim-schema/2/CIM_ResourceAllocationSettingData";
Xml.xpath_register_ns xpathctx
"vmw" "http://www.vmware.com/schema/ovf";
Xml.xpath_register_ns xpathctx
"vssd" "http://schemas.dmtf.org/wbem/wscim/1/cim-schema/2/CIM_VirtualSystemSettingData";
(* Parse the ovf file. *)
let name, memory, vcpu, firmware, disks, removables, nics =
parse_ovf_from_ova ovf in
let xpath_string = xpath_string xpathctx
and xpath_int = xpath_int xpathctx
and xpath_string_default = xpath_string_default xpathctx
and xpath_int_default = xpath_int_default xpathctx
and xpath_int64_default = xpath_int64_default xpathctx in
(* Search for vm name. *)
let name =
match xpath_string "/ovf:Envelope/ovf:VirtualSystem/ovf:Name/text()" with
| None | Some "" ->
warning (f_"could not parse ovf:Name from OVF document");
name_from_disk ova
match name with
| None ->
warning (f_"could not parse ovf:Name from OVF document");
name_from_disk ova
| Some name -> name in
(* Search for memory. *)
let memory = xpath_int64_default "/ovf:Envelope/ovf:VirtualSystem/ovf:VirtualHardwareSection/ovf:Item[rasd:ResourceType/text()=4]/rasd:VirtualQuantity/text()" (1024L *^ 1024L) in
let memory = memory *^ 1024L *^ 1024L in
let disks = List.map (
fun ({ href = href; compressed = compressed } as disk) ->
let partial =
if compressed && partial then (
(* We cannot access compressed disk inside the tar;
* we have to extract it.
*)
untar ~paths:[(subdirectory exploded ovf_folder) // href]
ova tmpdir;
false
)
else
partial in
(* Search for number of vCPUs. *)
let vcpu = xpath_int_default "/ovf:Envelope/ovf:VirtualSystem/ovf:VirtualHardwareSection/ovf:Item[rasd:ResourceType/text()=3]/rasd:VirtualQuantity/text()" 1 in
let filename =
if partial then
(subdirectory exploded ovf_folder) // href
else (
(* Does the file exist and is it readable? *)
Unix.access (ovf_folder // href) [Unix.R_OK];
ovf_folder // href
) in
(* BIOS or EFI firmware? *)
let firmware = xpath_string_default "/ovf:Envelope/ovf:VirtualSystem/ovf:VirtualHardwareSection/vmw:Config[@vmw:key=\"firmware\"]/@vmw:value" "bios" in
let firmware =
match firmware with
| "bios" -> BIOS
| "efi" -> UEFI
| s ->
error (f_"unknown Config:firmware value %s (expected \"bios\" or \"efi\")") s in
(* The spec allows the file to be gzip-compressed, in which case
* we must uncompress it into the tmpdir.
*)
let filename =
if compressed then (
let new_filename = tmpdir // String.random8 () ^ ".vmdk" in
let cmd =
sprintf "zcat %s > %s" (quote filename) (quote new_filename) in
if shell_command cmd <> 0 then
error (f_"error uncompressing %s, see earlier error messages")
filename;
new_filename
)
else filename in
(* Helper function to return the parent controller of a disk. *)
let parent_controller id =
let expr = sprintf "/ovf:Envelope/ovf:VirtualSystem/ovf:VirtualHardwareSection/ovf:Item[rasd:InstanceID/text()=%d]/rasd:ResourceType/text()" id in
let controller = xpath_int expr in
(* 6: iscsi controller, 5: ide *)
match controller with
| Some 6 -> Some Source_SCSI
| Some 5 -> Some Source_IDE
| None ->
warning (f_"ova disk has no parent controller, please report this as a bug supplying the *.ovf file extracted from the ova");
None
| Some controller ->
warning (f_"ova disk has an unknown VMware controller type (%d), please report this as a bug supplying the *.ovf file extracted from the ova")
controller;
None
in
(* Hard disks (ResourceType = 17). *)
let disks = ref [] in
let () =
let expr = "/ovf:Envelope/ovf:VirtualSystem/ovf:VirtualHardwareSection/ovf:Item[rasd:ResourceType/text()=17]" in
let obj = Xml.xpath_eval_expression xpathctx expr in
let nr_nodes = Xml.xpathobj_nr_nodes obj in
for i = 0 to nr_nodes-1 do
let n = Xml.xpathobj_node obj i in
Xml.xpathctx_set_current_context xpathctx n;
(* XXX We assume the OVF lists these in order.
let address = xpath_int "rasd:AddressOnParent/text()" in
*)
(* Find the parent controller. *)
let parent_id = xpath_int "rasd:Parent/text()" in
let controller =
match parent_id with
| None -> None
| Some id -> parent_controller id in
Xml.xpathctx_set_current_context xpathctx n;
let file_id = xpath_string_default "rasd:HostResource/text()" "" in
let rex = Str.regexp "^\\(ovf:\\)?/disk/\\(.*\\)" in
if Str.string_match rex file_id 0 then (
(* Chase the references through to the actual file name. *)
let file_id = Str.matched_group 2 file_id in
let expr = sprintf "/ovf:Envelope/ovf:DiskSection/ovf:Disk[@ovf:diskId='%s']/@ovf:fileRef" file_id in
let file_ref =
match xpath_string expr with
| None -> error (f_"error parsing disk fileRef")
| Some s -> s in
let expr = sprintf "/ovf:Envelope/ovf:References/ovf:File[@ovf:id='%s']/@ovf:href" file_ref in
let filename =
match xpath_string expr with
| None -> error (f_"no href in ovf:File (id=%s)") file_ref
| Some s -> s in
let expr = sprintf "/ovf:Envelope/ovf:References/ovf:File[@ovf:id='%s']/@ovf:compression" file_ref in
let compressed =
match xpath_string expr with
| None | Some "identity" -> false
| Some "gzip" -> true
| Some s -> error (f_"unsupported compression in OVF: %s") s in
let partial =
if compressed && partial then (
(* We cannot access compressed disk inside the tar;
* we have to extract it.
*)
untar ~paths:[(subdirectory exploded ovf_folder) // filename]
ova tmpdir;
false
)
else
partial in
let filename =
if partial then
(subdirectory exploded ovf_folder) // filename
else (
(* Does the file exist and is it readable? *)
Unix.access (ovf_folder // filename) [Unix.R_OK];
ovf_folder // filename
) in
(* The spec allows the file to be gzip-compressed, in which case
* we must uncompress it into the tmpdir.
*)
let filename =
if compressed then (
let new_filename = tmpdir // String.random8 () ^ ".vmdk" in
let cmd =
sprintf "zcat %s > %s" (quote filename) (quote new_filename) in
if shell_command cmd <> 0 then
error (f_"error uncompressing %s, see earlier error messages")
filename;
new_filename
)
else filename in
let qemu_uri =
if not partial then (
filename
)
else (
let offset, size =
try find_file_in_tar ova filename
with
| Not_found ->
error (f_"file '%s' not found in the ova") filename
| Failure msg -> error (f_"%s") msg in
(* QEMU requires size aligned to 512 bytes. This is safe because
* tar also works with 512 byte blocks.
*)
let size = roundup64 size 512L in
let doc = [
let qemu_uri =
if not partial then (
filename
)
else (
let offset, size =
try find_file_in_tar ova filename
with
| Not_found ->
error (f_"file '%s' not found in the ova") filename
| Failure msg -> error (f_"%s") msg in
(* QEMU requires size aligned to 512 bytes. This is safe because
* tar also works with 512 byte blocks.
*)
let size = roundup64 size 512L in
let doc = [
"file", JSON.Dict [
"driver", JSON.String "raw";
"offset", JSON.Int64 offset;
"size", JSON.Int64 size;
"file", JSON.Dict [
"driver", JSON.String "file";
"filename", JSON.String ova]
]
] in
let uri =
sprintf "json:%s" (JSON.string_of_doc ~fmt:JSON.Compact doc) in
debug "json: %s" uri;
uri
) in
"driver", JSON.String "raw";
"offset", JSON.Int64 offset;
"size", JSON.Int64 size;
"file", JSON.Dict [
"driver", JSON.String "file";
"filename", JSON.String ova]
]
] in
let uri =
sprintf "json:%s" (JSON.string_of_doc ~fmt:JSON.Compact doc) in
debug "json: %s" uri;
uri
) in
let disk = {
s_disk_id = i;
s_qemu_uri = qemu_uri;
s_format = Some "vmdk";
s_controller = controller;
} in
push_front disk disks;
) else
error (f_"could not parse disk rasd:HostResource from OVF document")
done in
let disks = List.rev !disks in
(* Floppies (ResourceType = 14), CDs (ResourceType = 15) and
* CDROMs (ResourceType = 16). (What is the difference?) Try hard
* to preserve the original ordering from the OVF.
*)
let removables = ref [] in
let () =
let expr =
"/ovf:Envelope/ovf:VirtualSystem/ovf:VirtualHardwareSection/ovf:Item[rasd:ResourceType/text()=14 or rasd:ResourceType/text()=15 or rasd:ResourceType/text()=16]" in
let obj = Xml.xpath_eval_expression xpathctx expr in
let nr_nodes = Xml.xpathobj_nr_nodes obj in
for i = 0 to nr_nodes-1 do
let n = Xml.xpathobj_node obj i in
Xml.xpathctx_set_current_context xpathctx n;
let id =
match xpath_int "rasd:ResourceType/text()" with
| None -> assert false
| Some (14|15|16 as i) -> i
| Some _ -> assert false in
let slot = xpath_int "rasd:AddressOnParent/text()" in
(* Find the parent controller. *)
let parent_id = xpath_int "rasd:Parent/text()" in
let controller =
match parent_id with
| None -> None
| Some id -> parent_controller id in
let typ =
match id with
| 14 -> Floppy
| 15 | 16 -> CDROM
| _ -> assert false in
let disk = {
s_removable_type = typ;
s_removable_controller = controller;
s_removable_slot = slot;
} in
push_front disk removables;
done in
let removables = List.rev !removables in
(* Search for networks ResourceType: 10 *)
let nics = ref [] in
let obj = Xml.xpath_eval_expression xpathctx "/ovf:Envelope/ovf:VirtualSystem/ovf:VirtualHardwareSection/ovf:Item[rasd:ResourceType/text()=10]" in
let nr_nodes = Xml.xpathobj_nr_nodes obj in
for i = 0 to nr_nodes-1 do
let n = Xml.xpathobj_node obj i in
Xml.xpathctx_set_current_context xpathctx n;
let vnet =
xpath_string_default "rasd:ElementName/text()" (sprintf"eth%d" i) in
let nic = {
s_mac = None;
s_nic_model = None;
s_vnet = vnet;
s_vnet_orig = vnet;
s_vnet_type = Network;
} in
push_front nic nics
done;
{ disk.source_disk with s_qemu_uri = qemu_uri }
) disks in
let source = {
s_hypervisor = VMware;
@@ -481,7 +316,7 @@ object
s_sound = None;
s_disks = disks;
s_removables = removables;
s_nics = List.rev !nics;
s_nics = nics;
} in
source

226
v2v/parse_ovf_from_ova.ml Normal file
View File

@@ -0,0 +1,226 @@
(* virt-v2v
* Copyright (C) 2009-2017 Red Hat Inc.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*)
(* Parse OVF from an externally produced OVA file. *)
open Common_gettext.Gettext
open Common_utils
open Unix_utils
open Types
open Utils
open Xpath_helpers
open Printf
type ovf_disk = {
source_disk : Types.source_disk;
href : string; (* The <File href> from the OVF file. *)
compressed : bool; (* If the file is gzip compressed. *)
}
let parse_ovf_from_ova ovf_filename =
let xml = read_whole_file ovf_filename in
let doc = Xml.parse_memory xml in
(* Handle namespaces. *)
let xpathctx = Xml.xpath_new_context doc in
Xml.xpath_register_ns xpathctx
"ovf" "http://schemas.dmtf.org/ovf/envelope/1";
Xml.xpath_register_ns xpathctx
"rasd" "http://schemas.dmtf.org/wbem/wscim/1/cim-schema/2/CIM_ResourceAllocationSettingData";
Xml.xpath_register_ns xpathctx
"vmw" "http://www.vmware.com/schema/ovf";
Xml.xpath_register_ns xpathctx
"vssd" "http://schemas.dmtf.org/wbem/wscim/1/cim-schema/2/CIM_VirtualSystemSettingData";
let xpath_string = xpath_string xpathctx
and xpath_int = xpath_int xpathctx
and xpath_string_default = xpath_string_default xpathctx
and xpath_int_default = xpath_int_default xpathctx
and xpath_int64_default = xpath_int64_default xpathctx in
let rec parse_top () =
(* Search for vm name. *)
let name =
match xpath_string "/ovf:Envelope/ovf:VirtualSystem/ovf:Name/text()" with
| None | Some "" -> None
| Some _ as name -> name in
(* Search for memory. *)
let memory = xpath_int64_default "/ovf:Envelope/ovf:VirtualSystem/ovf:VirtualHardwareSection/ovf:Item[rasd:ResourceType/text()=4]/rasd:VirtualQuantity/text()" (1024L *^ 1024L) in
let memory = memory *^ 1024L *^ 1024L in
(* Search for number of vCPUs. *)
let vcpu = xpath_int_default "/ovf:Envelope/ovf:VirtualSystem/ovf:VirtualHardwareSection/ovf:Item[rasd:ResourceType/text()=3]/rasd:VirtualQuantity/text()" 1 in
(* BIOS or EFI firmware? *)
let firmware = xpath_string_default "/ovf:Envelope/ovf:VirtualSystem/ovf:VirtualHardwareSection/vmw:Config[@vmw:key=\"firmware\"]/@vmw:value" "bios" in
let firmware =
match firmware with
| "bios" -> BIOS
| "efi" -> UEFI
| s ->
error (f_"unknown Config:firmware value %s (expected \"bios\" or \"efi\")") s in
name, memory, vcpu, firmware,
parse_disks (), parse_removables (), parse_nics ()
(* Helper function to return the parent controller of a disk. *)
and parent_controller id =
let expr = sprintf "/ovf:Envelope/ovf:VirtualSystem/ovf:VirtualHardwareSection/ovf:Item[rasd:InstanceID/text()=%d]/rasd:ResourceType/text()" id in
let controller = xpath_int expr in
(* 6: iscsi controller, 5: ide *)
match controller with
| Some 6 -> Some Source_SCSI
| Some 5 -> Some Source_IDE
| None ->
warning (f_"ova disk has no parent controller, please report this as a bug supplying the *.ovf file extracted from the ova");
None
| Some controller ->
warning (f_"ova disk has an unknown VMware controller type (%d), please report this as a bug supplying the *.ovf file extracted from the ova")
controller;
None
(* Hard disks (ResourceType = 17). *)
and parse_disks () =
let disks = ref [] in
let expr = "/ovf:Envelope/ovf:VirtualSystem/ovf:VirtualHardwareSection/ovf:Item[rasd:ResourceType/text()=17]" in
let obj = Xml.xpath_eval_expression xpathctx expr in
let nr_nodes = Xml.xpathobj_nr_nodes obj in
for i = 0 to nr_nodes-1 do
let n = Xml.xpathobj_node obj i in
Xml.xpathctx_set_current_context xpathctx n;
(* XXX We assume the OVF lists these in order.
let address = xpath_int "rasd:AddressOnParent/text()" in
*)
(* Find the parent controller. *)
let parent_id = xpath_int "rasd:Parent/text()" in
let controller =
match parent_id with
| None -> None
| Some id -> parent_controller id in
Xml.xpathctx_set_current_context xpathctx n;
let file_id = xpath_string_default "rasd:HostResource/text()" "" in
let rex = Str.regexp "^\\(ovf:\\)?/disk/\\(.*\\)" in
if Str.string_match rex file_id 0 then (
(* Chase the references through to the actual file name. *)
let file_id = Str.matched_group 2 file_id in
let expr = sprintf "/ovf:Envelope/ovf:DiskSection/ovf:Disk[@ovf:diskId='%s']/@ovf:fileRef" file_id in
let file_ref =
match xpath_string expr with
| None -> error (f_"error parsing disk fileRef")
| Some s -> s in
let expr = sprintf "/ovf:Envelope/ovf:References/ovf:File[@ovf:id='%s']/@ovf:href" file_ref in
let href =
match xpath_string expr with
| None -> error (f_"no href in ovf:File (id=%s)") file_ref
| Some s -> s in
let expr = sprintf "/ovf:Envelope/ovf:References/ovf:File[@ovf:id='%s']/@ovf:compression" file_ref in
let compressed =
match xpath_string expr with
| None | Some "identity" -> false
| Some "gzip" -> true
| Some s -> error (f_"unsupported compression in OVF: %s") s in
let disk = {
source_disk = {
s_disk_id = i;
s_qemu_uri = "";
s_format = Some "vmdk";
s_controller = controller;
};
href = href;
compressed = compressed
} in
push_front disk disks;
) else
error (f_"could not parse disk rasd:HostResource from OVF document")
done;
List.rev !disks
(* Floppies (ResourceType = 14), CDs (ResourceType = 15) and
* CDROMs (ResourceType = 16). (What is the difference?) Try hard
* to preserve the original ordering from the OVF.
*)
and parse_removables () =
let removables = ref [] in
let expr = "/ovf:Envelope/ovf:VirtualSystem/ovf:VirtualHardwareSection/ovf:Item[rasd:ResourceType/text()=14 or rasd:ResourceType/text()=15 or rasd:ResourceType/text()=16]" in
let obj = Xml.xpath_eval_expression xpathctx expr in
let nr_nodes = Xml.xpathobj_nr_nodes obj in
for i = 0 to nr_nodes-1 do
let n = Xml.xpathobj_node obj i in
Xml.xpathctx_set_current_context xpathctx n;
let id =
match xpath_int "rasd:ResourceType/text()" with
| None -> assert false
| Some (14|15|16 as i) -> i
| Some _ -> assert false in
let slot = xpath_int "rasd:AddressOnParent/text()" in
(* Find the parent controller. *)
let parent_id = xpath_int "rasd:Parent/text()" in
let controller =
match parent_id with
| None -> None
| Some id -> parent_controller id in
let typ =
match id with
| 14 -> Floppy
| 15 | 16 -> CDROM
| _ -> assert false in
let disk = {
s_removable_type = typ;
s_removable_controller = controller;
s_removable_slot = slot;
} in
push_front disk removables;
done;
List.rev !removables
(* Search for networks ResourceType: 10 *)
and parse_nics () =
let nics = ref [] in
let expr = "/ovf:Envelope/ovf:VirtualSystem/ovf:VirtualHardwareSection/ovf:Item[rasd:ResourceType/text()=10]" in
let obj = Xml.xpath_eval_expression xpathctx expr in
let nr_nodes = Xml.xpathobj_nr_nodes obj in
for i = 0 to nr_nodes-1 do
let n = Xml.xpathobj_node obj i in
Xml.xpathctx_set_current_context xpathctx n;
let vnet =
xpath_string_default "rasd:ElementName/text()" (sprintf"eth%d" i) in
let nic = {
s_mac = None;
s_nic_model = None;
s_vnet = vnet;
s_vnet_orig = vnet;
s_vnet_type = Network;
} in
push_front nic nics
done;
List.rev !nics
in
parse_top ()

View File

@@ -0,0 +1,36 @@
(* virt-v2v
* Copyright (C) 2009-2017 Red Hat Inc.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*)
(** Parse OVF from an externally produced OVA file.
This is used by [-i ova] only. OVA files are not a real standard
so we must make some assumptions here, eg. about disk format
being VMDK, which would not be true for oVirt. *)
type ovf_disk = {
source_disk : Types.source_disk;
href : string; (** The <File href> from the OVF file. *)
compressed : bool; (** If the href is gzip compressed. *)
}
(** A VMDK disk from a parsed OVF. *)
val parse_ovf_from_ova : string -> string option * int64 * int * Types.source_firmware * ovf_disk list * Types.source_removable list * Types.source_nic list
(** Parse an OVF file.
The returned tuple is
[name, memory, vcpu, firmware, disks, removables, nics] *)