mirror of
https://github.com/libguestfs/libguestfs.git
synced 2026-03-22 07:03:38 +00:00
OVA is not a particularly well-specified format. The specification allows a directory to be an OVA, so enable that. The spec doesn't mention that ZIP can be used in place of tar, but since we have seen these in the wild, allow that too.
277 lines
11 KiB
OCaml
277 lines
11 KiB
OCaml
(* virt-v2v
|
|
* Copyright (C) 2009-2014 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.
|
|
*)
|
|
|
|
open Printf
|
|
|
|
open Common_gettext.Gettext
|
|
open Common_utils
|
|
|
|
open Types
|
|
open Utils
|
|
|
|
class input_ova verbose ova =
|
|
let tmpdir =
|
|
let base_dir = (new Guestfs.guestfs ())#get_cachedir () in
|
|
let t = Mkdtemp.temp_dir ~base_dir "ova." "" in
|
|
rmdir_on_exit t;
|
|
t in
|
|
object
|
|
inherit input verbose
|
|
|
|
method as_options = "-i ova " ^ ova
|
|
|
|
method source () =
|
|
(* Extract ova file. *)
|
|
let exploded =
|
|
(* The spec allows a directory to be specified as an ova. This
|
|
* is also pretty convenient.
|
|
*)
|
|
if is_directory ova then ova
|
|
else (
|
|
match detect_file_type ova with
|
|
| `Tar ->
|
|
(* Normal ovas are tar file (not compressed). *)
|
|
let cmd = sprintf "tar -xf %s -C %s" (quote ova) (quote tmpdir) in
|
|
if verbose then printf "%s\n%!" cmd;
|
|
if Sys.command cmd <> 0 then
|
|
error (f_"error unpacking %s, see earlier error messages") ova;
|
|
tmpdir
|
|
| `Zip ->
|
|
(* However, although not permitted by the spec, people ship
|
|
* zip files as ova too.
|
|
*)
|
|
let cmd = sprintf "unzip%s -j -d %s %s"
|
|
(if verbose then "" else " -q")
|
|
(quote tmpdir) (quote ova) in
|
|
if verbose then printf "%s\n%!" cmd;
|
|
if Sys.command cmd <> 0 then
|
|
error (f_"error unpacking %s, see earlier error messages") ova;
|
|
tmpdir
|
|
| `GZip | `XZ | `Unknown ->
|
|
error (f_"%s: unsupported file format") ova
|
|
) in
|
|
|
|
let files = Sys.readdir exploded in
|
|
let ovf = ref "" in
|
|
(* Search for the ovf file. *)
|
|
Array.iter (
|
|
fun file ->
|
|
if Filename.check_suffix file ".ovf" then ovf := file
|
|
) files;
|
|
let ovf = !ovf in
|
|
if ovf = "" then
|
|
error (f_"no .ovf file was found in %s") ova;
|
|
|
|
(* Read any .mf (manifest) files and verify sha1. *)
|
|
let rex = Str.regexp "SHA1(\\(.*\\))=\\([0-9a-fA-F]+\\)\r?" in
|
|
Array.iter (
|
|
fun mf ->
|
|
if Filename.check_suffix mf ".mf" then (
|
|
let chan = open_in (exploded // mf) in
|
|
let rec loop () =
|
|
let line = input_line chan in
|
|
if Str.string_match rex line 0 then (
|
|
let disk = Str.matched_group 1 line in
|
|
let expected = Str.matched_group 2 line in
|
|
let cmd = sprintf "sha1sum %s" (quote (exploded // disk)) in
|
|
let out = external_command ~prog cmd in
|
|
match out with
|
|
| [] ->
|
|
error (f_"no output from sha1sum command, see previous errors")
|
|
| [line] ->
|
|
let actual, _ = string_split " " line in
|
|
if actual <> expected then
|
|
error (f_"checksum of disk %s does not match manifest %s (actual sha1(%s) = %s, expected sha1 (%s) = %s)")
|
|
disk mf disk actual disk expected;
|
|
if verbose then
|
|
printf "sha1 of %s matches expected checksum %s\n%!"
|
|
disk expected
|
|
| _::_ -> error (f_"cannot parse output of sha1sum command")
|
|
)
|
|
in
|
|
(try loop () with End_of_file -> ());
|
|
close_in chan
|
|
)
|
|
) files;
|
|
|
|
(* Parse the ovf file. *)
|
|
let xml = read_whole_file (exploded // 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
|
|
"vssd" "http://schemas.dmtf.org/wbem/wscim/1/cim-schema/2/CIM_VirtualSystemSettingData";
|
|
|
|
let xpath_to_string expr default =
|
|
let obj = Xml.xpath_eval_expression xpathctx expr in
|
|
if Xml.xpathobj_nr_nodes obj < 1 then default
|
|
else (
|
|
let node = Xml.xpathobj_node doc obj 0 in
|
|
Xml.node_as_string node
|
|
)
|
|
and xpath_to_int expr default =
|
|
let obj = Xml.xpath_eval_expression xpathctx expr in
|
|
if Xml.xpathobj_nr_nodes obj < 1 then default
|
|
else (
|
|
let node = Xml.xpathobj_node doc obj 0 in
|
|
let str = Xml.node_as_string node in
|
|
try int_of_string str
|
|
with Failure "int_of_string" ->
|
|
error (f_"expecting XML expression to return an integer (expression: %s)")
|
|
expr
|
|
)
|
|
in
|
|
|
|
(* Search for vm name. *)
|
|
let name =
|
|
xpath_to_string "/ovf:Envelope/ovf:VirtualSystem/ovf:Name/text()" "" in
|
|
if name = "" then
|
|
error (f_"could not parse ovf:Name from OVF document");
|
|
|
|
(* Search for memory. *)
|
|
let memory = xpath_to_int "/ovf:Envelope/ovf:VirtualSystem/ovf:VirtualHardwareSection/ovf:Item[rasd:ResourceType/text()=4]/rasd:VirtualQuantity/text()" (1024 * 1024) in
|
|
let memory = Int64.of_int (memory * 1024 * 1024) in
|
|
|
|
(* Search for number of vCPUs. *)
|
|
let vcpu = xpath_to_int "/ovf:Envelope/ovf:VirtualSystem/ovf:VirtualHardwareSection/ovf:Item[rasd:ResourceType/text()=3]/rasd:VirtualQuantity/text()" 1 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 doc obj i in
|
|
Xml.xpathctx_set_current_context xpathctx n;
|
|
let address = xpath_to_int "rasd:AddressOnParent/text()" 0 in
|
|
let parent_id = xpath_to_int "rasd:Parent/text()" 0 in
|
|
(* Probably the parent controller. *)
|
|
let expr = sprintf "/ovf:Envelope/ovf:VirtualSystem/ovf:VirtualHardwareSection/ovf:Item[rasd:InstanceId/text()=%d]/rasd:ResourceType/text()" parent_id in
|
|
let controller = xpath_to_int expr 0 in
|
|
(* 6: iscsi controller, 5: ide. assuming scsi or ide *)
|
|
let target_dev =
|
|
match controller with
|
|
| 6 -> "sd"
|
|
| 0 | 5 | _ (* XXX floppy should be 'fd'? *) -> "hd" in
|
|
|
|
let target_dev = target_dev ^ drive_name address in
|
|
|
|
Xml.xpathctx_set_current_context xpathctx n;
|
|
let file_id = xpath_to_string "rasd:HostResource/text()" "" in
|
|
let rex = Str.regexp "^ovf:/disk/\\(.*\\)" in
|
|
if Str.string_match rex file_id 0 then (
|
|
let file_id = Str.matched_group 1 file_id in
|
|
let expr = sprintf "/ovf:Envelope/ovf:DiskSection/ovf:Disk[@ovf:diskId='%s']/@ovf:fileRef" file_id in
|
|
let file_ref = xpath_to_string expr "" in
|
|
if file_ref == "" then error (f_"error parsing disk fileRef");
|
|
let expr = sprintf "/ovf:Envelope/ovf:References/ovf:File[@ovf:id='%s']/@ovf:href" file_ref in
|
|
let file_name = xpath_to_string expr "" in
|
|
let disk = {
|
|
s_qemu_uri= exploded // file_name;
|
|
s_format = Some "vmdk";
|
|
s_target_dev = Some target_dev;
|
|
} in
|
|
disks := 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 doc obj i in
|
|
Xml.xpathctx_set_current_context xpathctx n;
|
|
let id = xpath_to_int "rasd:ResourceType/text()" 0 in
|
|
assert (id = 14 || id = 15 || id = 16);
|
|
let address = xpath_to_int "rasd:AddressOnParent/text()" 0 in
|
|
let parent_id = xpath_to_int "rasd:Parent/text()" 0 in
|
|
(* Probably the parent controller. *)
|
|
let expr = sprintf "/ovf:Envelope/ovf:VirtualSystem/ovf:VirtualHardwareSection/ovf:Item[rasd:InstanceId/text()=%d]/rasd:ResourceType/text()" parent_id in
|
|
let controller = xpath_to_int expr 0 in
|
|
(* 6: iscsi controller, 5: ide. assuming scsi or ide *)
|
|
let target_dev =
|
|
match controller with
|
|
| 6 -> "sd"
|
|
| 0 | 5 | _ (* XXX floppy should be 'fd'? *) -> "hd" in
|
|
|
|
let target_dev = target_dev ^ drive_name address in
|
|
|
|
let typ =
|
|
match id with
|
|
| 14 -> `Floppy
|
|
| 15 | 16 -> `CDROM
|
|
| _ -> assert false in
|
|
let disk = {
|
|
s_removable_type = typ;
|
|
s_removable_target_dev = Some target_dev
|
|
} in
|
|
removables := 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 doc obj i in
|
|
Xml.xpathctx_set_current_context xpathctx n;
|
|
let vnet = xpath_to_string "rasd:ElementName/text()" (sprintf"eth%d" i) in
|
|
let nic = {
|
|
s_mac = None;
|
|
s_vnet = vnet;
|
|
s_vnet_orig = vnet;
|
|
s_vnet_type = Network;
|
|
} in
|
|
nics := nic :: !nics
|
|
done;
|
|
|
|
let source = {
|
|
s_dom_type = "vmware";
|
|
s_name = name;
|
|
s_orig_name = name;
|
|
s_memory = memory;
|
|
s_vcpu = vcpu;
|
|
s_features = []; (* XXX *)
|
|
s_display = None; (* XXX *)
|
|
s_disks = disks;
|
|
s_removables = removables;
|
|
s_nics = List.rev !nics;
|
|
} in
|
|
|
|
source
|
|
end
|
|
|
|
let input_ova = new input_ova
|
|
let () = Modules_list.register_input_module "ova"
|