[Pkg-libvirt-commits] [libguestfs] 58/266: v2v: Add RHEV target.

Hilko Bengen bengen at moszumanska.debian.org
Fri Oct 3 14:41:41 UTC 2014


This is an automated email from the git hooks/post-receive script.

bengen pushed a commit to annotated tag debian/1%1.27.35-1
in repository libguestfs.

commit 40cecb4b9cb4354902149f68918fe1d24a534c41
Author: Richard W.M. Jones <rjones at redhat.com>
Date:   Mon Aug 4 17:20:41 2014 +0100

    v2v: Add RHEV target.
    
    Use:
    
      virt-v2v [..] -o rhev -os nfs:/export
    
    or:
    
      virt-v2v [..] -o rhev -os /mountpoint
    
    to write to a RHEV-M / oVirt Export Storage Domain.
---
 po/POTFILES-ml      |   2 +
 v2v/DOM.ml          |  97 +++++++++
 v2v/DOM.mli         |  52 +++++
 v2v/Makefile.am     |   6 +
 v2v/cmdline.ml      |  15 +-
 v2v/target_RHEV.ml  | 568 ++++++++++++++++++++++++++++++++++++++++++++++++++++
 v2v/target_RHEV.mli |  24 +++
 v2v/types.ml        |   5 +-
 v2v/types.mli       |   7 +-
 v2v/utils.ml        |   6 +
 v2v/v2v.ml          |  14 +-
 v2v/virt-v2v.pod    |  21 +-
 12 files changed, 805 insertions(+), 12 deletions(-)

diff --git a/po/POTFILES-ml b/po/POTFILES-ml
index fe6f169..2c2b71b 100644
--- a/po/POTFILES-ml
+++ b/po/POTFILES-ml
@@ -82,12 +82,14 @@ sysprep/sysprep_operation_udev_persistent_net.ml
 sysprep/sysprep_operation_user_account.ml
 sysprep/sysprep_operation_utmp.ml
 sysprep/sysprep_operation_yum_uuid.ml
+v2v/DOM.ml
 v2v/cmdline.ml
 v2v/convert_linux.ml
 v2v/convert_windows.ml
 v2v/lib_linux.ml
 v2v/source_libvirt.ml
 v2v/stringMap.ml
+v2v/target_RHEV.ml
 v2v/target_local.ml
 v2v/types.ml
 v2v/utils.ml
diff --git a/v2v/DOM.ml b/v2v/DOM.ml
new file mode 100644
index 0000000..d828db4
--- /dev/null
+++ b/v2v/DOM.ml
@@ -0,0 +1,97 @@
+(* 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.
+ *)
+
+(* Poor man's XML DOM, mutable for easy of modification. *)
+
+open Common_utils
+open Utils
+
+open Printf
+
+type node =
+  | PCData of string
+  | Element of element
+and element = {
+  e_name : string;                      (* Name of element. *)
+  mutable e_attrs : attr list;          (* Attributes. *)
+  mutable e_children : node list;       (* Child elements. *)
+}
+and attr = string * string
+and doc = element
+
+let doc name attrs children =
+  { e_name = name; e_attrs = attrs; e_children = children }
+
+let e name attrs children =
+  Element { e_name = name; e_attrs = attrs; e_children = children }
+
+let rec node_to_chan chan = function
+  | PCData str -> output_string chan (xml_quote_pcdata str)
+  | Element e -> element_to_chan chan e
+and element_to_chan chan
+    { e_name = name; e_attrs = attrs; e_children = children } =
+  fprintf chan "<%s" name;
+  List.iter (fun (n, v) -> fprintf chan " %s='%s'" n (xml_quote_attr v)) attrs;
+  output_char chan '>';
+  List.iter (node_to_chan chan) children;
+  fprintf chan "</%s>" name
+
+let doc_to_chan chan doc =
+  fprintf chan "<?xml version='1.0' encoding='utf-8'?>\n";
+  element_to_chan chan doc
+
+let path_to_nodes doc path =
+  match path with
+  | [] -> invalid_arg "path_to_nodes: empty path"
+  | top_name :: path ->
+    if doc.e_name <> top_name then []
+    else (
+      let rec loop nodes path =
+        match path with
+        | [] -> []
+        | [p] ->
+          List.filter (
+            function
+            | PCData _ -> false
+            | Element e when e.e_name = p -> true
+            | Element _ -> false
+          ) nodes
+        | p :: ps ->
+          let children =
+            filter_map (
+              function
+              | PCData _ -> None
+              | Element e when e.e_name = p -> Some e.e_children
+              | Element _ -> None
+            ) nodes in
+          List.concat (List.map (fun nodes -> loop nodes ps) children)
+      in
+      loop doc.e_children path
+    )
+
+let filter_node_list_by_attr nodes attr =
+  List.filter (
+    function
+    | Element { e_attrs = attrs } when List.mem attr attrs -> true
+    | Element _ | PCData _ -> false
+  ) nodes
+
+let find_node_by_attr nodes attr =
+  match filter_node_list_by_attr nodes attr with
+  | [] -> raise Not_found
+  | x::_ -> x
diff --git a/v2v/DOM.mli b/v2v/DOM.mli
new file mode 100644
index 0000000..2c97f5c
--- /dev/null
+++ b/v2v/DOM.mli
@@ -0,0 +1,52 @@
+(* 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.
+ *)
+
+(** Poor man's XML DOM, mutable for easy of modification. *)
+
+type node =
+  | PCData of string
+  | Element of element
+and element = {
+  e_name : string;                      (** Name of element. *)
+  mutable e_attrs : attr list;          (** Attributes. *)
+  mutable e_children : node list;       (** Child elements. *)
+}
+and attr = string * string
+and doc = element
+
+val doc : string -> attr list -> node list -> doc
+(** A quick way to create a document. *)
+
+val e : string -> attr list -> node list -> node
+(** A quick way to create elements. *)
+
+val doc_to_chan : out_channel -> doc -> unit
+(** Write the XML document to an output channel. *)
+
+val path_to_nodes : doc -> string list -> node list
+(** Search down the path and return a list of all matching elements.
+    Returns an empty list if none were found. *)
+
+val filter_node_list_by_attr : node list -> attr -> node list
+(** Find DOM elements which have a particular attribute name=value (not
+    recursively).  If not found, returns an empty list. *)
+
+val find_node_by_attr : node list -> attr -> node
+(** Find the first DOM element which has a particular attribute
+    name=value (not recursively).  If not found, raises
+    [Not_found]. *)
diff --git a/v2v/Makefile.am b/v2v/Makefile.am
index 0d8ef09..97d6d3e 100644
--- a/v2v/Makefile.am
+++ b/v2v/Makefile.am
@@ -26,9 +26,11 @@ CLEANFILES = *~ *.cmi *.cmo *.cmx *.cmxa *.o virt-v2v
 SOURCES_MLI = \
 	convert_linux.mli \
 	convert_windows.mli \
+	DOM.mli \
 	lib_linux.mli \
 	source_libvirt.mli \
 	target_local.mli \
+	target_RHEV.mli \
 	types.mli \
 	xml.mli
 
@@ -37,18 +39,21 @@ SOURCES_ML = \
 	types.ml \
 	utils.ml \
 	xml.ml \
+	DOM.ml \
 	lib_linux.ml \
 	cmdline.ml \
 	source_libvirt.ml \
 	convert_linux.ml \
 	convert_windows.ml \
 	target_local.ml \
+	target_RHEV.ml \
 	v2v.ml
 
 SOURCES_C = \
 	$(top_builddir)/fish/progress.c \
 	$(top_builddir)/mllib/tty-c.c \
 	$(top_builddir)/mllib/progress-c.c \
+	$(top_builddir)/mllib/mkdtemp-c.c \
 	$(top_builddir)/customize/crypt-c.c \
 	utils-c.c \
 	xml-c.c
@@ -73,6 +78,7 @@ BOBJECTS = \
 	$(top_builddir)/mllib/tTY.cmo \
 	$(top_builddir)/mllib/progress.cmo \
 	$(top_builddir)/mllib/config.cmo \
+	$(top_builddir)/mllib/mkdtemp.cmo \
 	$(top_builddir)/customize/urandom.cmo \
 	$(top_builddir)/customize/random_seed.cmo \
 	$(top_builddir)/customize/hostname.cmo \
diff --git a/v2v/cmdline.ml b/v2v/cmdline.ml
index 52a4959..0d46fc6 100644
--- a/v2v/cmdline.ml
+++ b/v2v/cmdline.ml
@@ -42,6 +42,7 @@ let parse_cmdline () =
   let quiet = ref false in
   let verbose = ref false in
   let trace = ref false in
+  let vmtype = ref "" in
 
   let input_mode = ref `Libvirt in
   let set_input_mode = function
@@ -98,6 +99,7 @@ let parse_cmdline () =
     "--verbose", Arg.Set verbose,           ditto;
     "-V",        Arg.Unit display_version,  " " ^ s_"Display version and exit";
     "--version", Arg.Unit display_version,  ditto;
+    "--vmtype",  Arg.Set_string vmtype,     "server|desktop " ^ s_"Set vmtype (for RHEV)";
     "-x",        Arg.Set trace,             " " ^ s_"Enable tracing of libguestfs calls";
   ] in
   long_options := argspec;
@@ -139,6 +141,13 @@ read the man page virt-v2v(1).
   let root_choice = !root_choice in
   let verbose = !verbose in
   let trace = !trace in
+  let vmtype =
+    match !vmtype with
+    | "server" -> Some `Server
+    | "desktop" -> Some `Desktop
+    | "" -> None
+    | _ ->
+      error (f_"unknown --vmtype option, must be \"server\" or \"desktop\"") in
 
   (* No arguments and machine-readable mode?  Print out some facts
    * about what this binary supports.
@@ -177,6 +186,8 @@ read the man page virt-v2v(1).
     | `Libvirt ->
       if output_storage <> "" then
         error (f_"-o libvirt: do not use the -os option");
+      if vmtype <> None then
+        error (f_"--vmtype option can only be used with '-o rhev'");
       OutputLibvirt output_conn
     | `Local ->
       if output_storage = "" then
@@ -184,11 +195,13 @@ read the man page virt-v2v(1).
       if not (is_directory output_storage) then
         error (f_"-os %s: output directory does not exist or is not a directory")
           output_storage;
+      if vmtype <> None then
+        error (f_"--vmtype option can only be used with '-o rhev'");
       OutputLocal output_storage
     | `RHEV ->
       if output_storage = "" then
         error (f_"-o local: output storage was not specified, use '-os'");
-      OutputRHEV output_storage in
+      OutputRHEV (output_storage, vmtype) in
 
   input, output,
   debug_gc, output_alloc, output_format, output_name,
diff --git a/v2v/target_RHEV.ml b/v2v/target_RHEV.ml
new file mode 100644
index 0000000..d3f360d
--- /dev/null
+++ b/v2v/target_RHEV.ml
@@ -0,0 +1,568 @@
+(* 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 Common_gettext.Gettext
+open Common_utils
+
+open Unix
+open Printf
+
+open Types
+open Utils
+open DOM
+
+let title = sprintf "Exported by virt-v2v %s" Config.package_version
+
+(* Describes a mounted Export Storage Domain. *)
+type export_storage_domain = {
+  mp : string;                          (* Local mountpoint. *)
+  uuid : string;                        (* /mp/uuid *)
+}
+
+(* Private data that we keep between calls.  Keep it in a global
+ * variables since there's only one conversion going on.
+ *)
+(* Export Storage Domain mountpoint. *)
+let esd = ref { mp = ""; uuid = "" }
+(* Target image directory, UUID. *)
+let image_uuid = ref ""
+let image_dir = ref ""
+(* Flag to indicate if the target image (image_dir) should be
+ * deleted.  This is set to false once we know the conversion was
+ * successful.
+ *)
+let delete_target_directory = ref true
+(* We set the creation time to be the same for all dates in
+ * all metadata files.
+ *)
+let time = time ()
+let iso_time =
+  let tm = gmtime time in
+  sprintf "%04d/%02d/%02d %02d:%02d:%02d"
+    (tm.tm_year + 1900) (tm.tm_mon + 1) tm.tm_mday
+    tm.tm_hour tm.tm_min tm.tm_sec
+
+(* This is called early on in the conversion and lets us choose the
+ * name of the target files that eventually get written by the main
+ * code.
+ *
+ * 'os' is the output storage (-os nfs:/export).  'source' contains a
+ * few useful fields such as the guest name.  'overlays' describes the
+ * destination files.  We modify and return this list.
+ *
+ * Note it's good to fail here (early) if there are any problems, since
+ * the next time we are called (in {!create_metadata}) we have already
+ * done the conversion and copy, and the user won't thank us for
+ * displaying errors there.
+ *)
+let rec initialize ~verbose os source output_alloc overlays =
+  esd := mount_and_check_export_storage_domain ~verbose os;
+  if verbose then
+    eprintf "RHEV: ESD mountpoint: %s\nRHEV: ESD UUID: %s\n%!" !esd.mp !esd.uuid;
+
+  (* Create a unique UUID for the final image. *)
+  image_uuid := uuidgen ~prog ();
+  image_dir := !esd.mp // !esd.uuid // "images" // !image_uuid;
+
+  (* We need to create the target image directory so there's a place
+   * for the main program to copy the images to.  However if image
+   * conversion fails for any reason then we delete this directory.
+   *)
+  mkdir !image_dir 0o755;
+  at_exit (fun () ->
+    if !delete_target_directory then (
+      let cmd = sprintf "rm -rf %s" (quote !image_dir) in
+      ignore (Sys.command cmd)
+    )
+  );
+  if verbose then
+    eprintf "RHEV: export directory: %s\n%!" !image_dir;
+
+  (* This loop has two purposes: (1) Generate the randomly named
+   * target files (just the names).  (2) Generate the .meta file
+   * associated with each volume.  At the end we have a directory
+   * structure like this:
+   *   /<MP>/<ESD_UUID>/images/<IMAGE_UUID>/
+   *      <VOL_UUID_1>        # first disk - will be created by main code
+   *      <VOL_UUID_1>.meta   # first disk
+   *      <VOL_UUID_2>        # second disk - will be created by main code
+   *      <VOL_UUID_2>.meta   # second disk
+   *      <VOL_UUID_3>        # etc
+   *      <VOL_UUID_3>.meta   #
+   *)
+  let overlays =
+    let output_alloc_for_rhev =
+      match output_alloc with
+      | `Sparse -> "SPARSE"
+      | `Preallocated -> "PREALLOCATED" in
+
+    List.map (
+      fun ov ->
+        let vol_uuid = uuidgen ~prog () in
+        let target_file = !image_dir // vol_uuid in
+
+        if verbose then
+          eprintf "RHEV: will export %s to %s\n%!" ov.ov_sd target_file;
+
+        (* Create the per-volume metadata (.meta files, in an oVirt-
+         * specific format).
+         *)
+        let vol_meta = target_file ^ ".meta" in
+
+        let size_in_sectors =
+          if ov.ov_virtual_size &^ 511L <> 0L then
+            error (f_"the virtual size of the input disk %s is not an exact multiple of 512 bytes.  The virtual size is: %Ld.\n\nThis probably means something unexpected is going on, so please file a bug about this issue.") ov.ov_source_file ov.ov_virtual_size;
+          ov.ov_virtual_size /^ 512L in
+
+        let format_for_rhev =
+          match ov.ov_target_format with
+          | "raw" -> "RAW"
+          | "qcow2" -> "COW"
+          | _ ->
+            error (f_"RHEV does not support the output format '%s', only raw or qcow2") ov.ov_target_format in
+
+        let chan = open_out vol_meta in
+        let fpf fs = fprintf chan fs in
+        fpf "DOMAIN=%s\n" !esd.uuid; (* "Domain" as in Export Storage Domain *)
+        fpf "VOLTYPE=LEAF\n";
+        fpf "CTIME=%.0f\n" time;
+        fpf "MTIME=%.0f\n" time;
+        fpf "IMAGE=%s\n" !image_uuid;
+        fpf "DISKTYPE=1\n";
+        fpf "PUUID=00000000-0000-0000-0000-000000000000\n";
+        fpf "LEGALITY=LEGAL\n";
+        fpf "POOL_UUID=\n";
+        fpf "SIZE=%Ld\n" size_in_sectors;
+        fpf "FORMAT=%s\n" format_for_rhev;
+        fpf "TYPE=%s\n" output_alloc_for_rhev;
+        fpf "DESCRIPTION=%s\n" title;
+        fpf "EOF\n";
+        close_out chan;
+
+        { ov with
+          ov_target_file_tmp = target_file; ov_target_file = target_file;
+          ov_vol_uuid = vol_uuid }
+    ) overlays in
+
+  (* Return the list of overlays. *)
+  overlays
+
+and mount_and_check_export_storage_domain ~verbose os =
+  (* The user can either specify -os nfs:/export, or a local directory
+   * which is assumed to be the already-mounted NFS export.  In either
+   * case we need to check that we have sufficient permissions to write
+   * to this mountpoint.
+   *)
+  match string_split ":/" os with
+  | mp, "" ->                        (* Already mounted directory. *)
+    check_export_storage_domain os mp
+  | server, export ->
+    let export = "/" ^ export in
+
+    (* Try mounting it. *)
+    let mp = Mkdtemp.temp_dir "v2v." "" in
+    let cmd =
+      sprintf "mount %s:%s %s" (quote server) (quote export) (quote mp) in
+    if verbose then printf "%s\n%!" cmd;
+    if Sys.command cmd <> 0 then
+      error (f_"mount command failed, see earlier errors.\n\nThis probably means you didn't specify the right Export Storage Domain path [-os %s], or else you need to rerun virt-v2v as root.") os;
+
+    (* Make sure it is unmounted at exit. *)
+    at_exit (fun () ->
+      let cmd = sprintf "umount %s" (quote mp) in
+      if verbose then printf "%s\n%!" cmd;
+      ignore (Sys.command cmd);
+      try rmdir mp with _ -> ()
+    );
+
+    check_export_storage_domain os mp
+
+and check_export_storage_domain os mp =
+  (* Typical ESD mountpoint looks like this:
+   * $ ls /tmp/mnt
+   * 39b6af0e-1d64-40c2-97e4-4f094f1919c7  __DIRECT_IO_TEST__  lost+found
+   * $ ls /tmp/mnt/39b6af0e-1d64-40c2-97e4-4f094f1919c7
+   * dom_md  images  master
+   * We expect exactly one of those magic UUIDs.
+   *)
+  let entries =
+    try Sys.readdir mp
+    with Sys_error msg ->
+      error (f_"could not read the Export Storage Domain specified by the '-os %s' parameter on the command line.  Is it really an OVirt or RHEV-M Export Storage Domain?  The original error is: %s") os msg in
+  let entries = Array.to_list entries in
+  let uuids = List.filter (
+    fun entry ->
+      String.length entry = 36 &&
+      entry.[8] = '-' && entry.[13] = '-' && entry.[18] = '-' &&
+      entry.[23] = '-'
+  ) entries in
+  let uuid =
+    match uuids with
+    | [uuid] -> uuid
+    | [] ->
+      error (f_"there are no UUIDs in the Export Storage Domain (%s).  Is it really an OVirt or RHEV-M Export Storage Domain?") os
+    | _::_ ->
+      error (f_"there are multiple UUIDs in the Export Storage Domain (%s).  This is unexpected, and may be a bug in virt-v2v or OVirt.") os in
+
+  (* Check that the domain has been attached to a Data Center by checking that
+   * the master/vms directory exists.
+   *)
+  if not (is_directory (mp // uuid // "master" // "vms")) then
+    error (f_"the Export Storage Domain (%s) has not been attached to any Data Center.\n\nYou have to do this through the RHEV-M / OVirt user interface first.") os;
+
+  (* Check that the ESD is writable. *)
+  let testfile = mp // uuid // "v2v-write-test" in
+  let write_test_failed err =
+    error (f_"the Export Storage Domain (%s) is not writable.\n\nThis probably means you need to run virt-v2v as 'root'.\n\nOriginal error was: %s") os err;
+  in
+  (try
+     let chan = open_out testfile in
+     close_out chan;
+     unlink testfile
+   with
+   | Sys_error err -> write_test_failed err
+   | Unix_error (code, _, _) -> write_test_failed (error_message code)
+  );
+
+  (* Looks good, so return the ESD object. *)
+  { mp = mp; uuid = uuid }
+
+(* This is called after conversion to write the OVF metadata. *)
+let rec create_metadata os vmtype source output_alloc
+    overlays inspect guestcaps =
+  let vm_uuid = uuidgen ~prog () in
+
+  let memsize_mb = source.s_memory /^ 1024L /^ 1024L in
+
+  let vmtype =
+    match vmtype with
+    | Some vmtype -> vmtype
+    | None -> get_vmtype inspect in
+  let vmtype = match vmtype with `Desktop -> "DESKTOP" | `Server -> "SERVER" in
+  let ostype = get_ostype inspect in
+
+  let ovf : doc =
+    doc "ovf:Envelope" [
+      "xmlns:rasd", "http://schemas.dmtf.org/wbem/wscim/1/cim-schema/2/CIM_ResourceAllocationSettingData";
+      "xmlns:vssd", "http://schemas.dmtf.org/wbem/wscim/1/cim-schema/2/CIM_VirtualSystemSettingData";
+      "xmlns:xsi", "http://www.w3.org/2001/XMLSchema-instance";
+      "xmlns:ovf", "http://schemas.dmtf.org/ovf/envelope/1/";
+      "ovf:version", "0.9"
+    ] [
+      e "References" [] [];
+      e "Section" ["xsi:type", "ovf:NetworkSection_Type"] [
+        e "Info" [] [PCData "List of networks"]
+      ];
+      e "Section" ["xsi:type", "ovf:DiskSection_Type"] [
+        e "Info" [] [PCData "List of Virtual Disks"]
+      ];
+      e "Content" ["ovf:id", "out"; "xsi:type", "ovf:VirtualSystem_Type"] [
+        e "Name" [] [PCData source.s_name];
+        e "TemplateId" [] [PCData "00000000-0000-0000-0000-000000000000"];
+        e "TemplateName" [] [PCData "Blank"];
+        e "Description" [] [PCData title];
+        e "Domain" [] [];
+        e "CreationDate" [] [PCData iso_time];
+        e "IsInitilized" [] [PCData "True"];
+        e "IsAutoSuspend" [] [PCData "False"];
+        e "TimeZone" [] [];
+        e "IsStateless" [] [PCData "False"];
+        e "Origin" [] [PCData "0"];
+        e "VmType" [] [PCData vmtype];
+        e "DefaultDisplayType" [] [PCData "1"];
+
+        e "Section" ["ovf:id", vm_uuid; "ovf:required", "false";
+                     "xsi:type", "ovf:OperatingSystemSection_Type"] [
+          e "Info" [] [PCData "Guest Operating System"];
+          e "Description" [] [PCData ostype];
+        ];
+
+        e "Section" ["xsi:type", "ovf:VirtualHardwareSection_Type"] [
+          e "Info" [] [PCData (sprintf "%d CPU, %Ld Memory" source.s_vcpu memsize_mb)];
+          e "Item" [] [
+            e "rasd:Caption" [] [PCData (sprintf "%d virtual cpu" source.s_vcpu)];
+            e "rasd:Description" [] [PCData "Number of virtual CPU"];
+            e "rasd:InstanceId" [] [PCData "1"];
+            e "rasd:ResourceType" [] [PCData "3"];
+            e "rasd:num_of_sockets" [] [PCData (string_of_int source.s_vcpu)];
+            e "rasd:cpu_per_socket"[] [PCData "1"];
+          ];
+          e "Item" [] [
+            e "rasd:Caption" [] [PCData (sprintf "%Ld MB of memory" memsize_mb)];
+            e "rasd:Description" [] [PCData "Memory Size"];
+            e "rasd:InstanceId" [] [PCData "2"];
+            e "rasd:ResourceType" [] [PCData "4"];
+            e "rasd:AllocationUnits" [] [PCData "MegaBytes"];
+            e "rasd:VirtualQuantity" [] [PCData (Int64.to_string memsize_mb)];
+          ];
+          e "Item" [] [
+            e "rasd:Caption" [] [PCData "USB Controller"];
+            e "rasd:InstanceId" [] [PCData "4"];
+            e "rasd:ResourceType" [] [PCData "23"];
+            e "rasd:UsbPolicy" [] [PCData "Disabled"];
+          ];
+          e "Item" [] [
+            e "rasd:Caption" [] [PCData "Graphical Controller"];
+            e "rasd:InstanceId" [] [PCData "5"];
+            e "rasd:ResourceType" [] [PCData "20"];
+            e "rasd:VirtualQuantity" [] [PCData "1"];
+            e "rasd:Device" [] [PCData "qxl"];
+          ]
+        ]
+      ]
+    ] in
+
+  (* Add disks to the OVF XML. *)
+  add_disks output_alloc overlays guestcaps ovf;
+
+  (* XXX Missing here from old virt-v2v:
+     cdroms and floppies
+     network interfaces
+     display
+     See: lib/Sys/VirtConvert/Connection/RHEVTarget.pm
+  *)
+
+
+
+
+  (* Write it to the metadata file. *)
+  let dir = !esd.mp // !esd.uuid // "master" // "vms" // vm_uuid in
+  mkdir dir 0o755;
+  let file = dir // vm_uuid ^ ".ovf" in
+  let chan = open_out file in
+  doc_to_chan chan ovf;
+  close_out chan;
+
+  (* Finished, so don't delete the target directory on exit. *)
+  delete_target_directory := false
+
+(* Guess vmtype based on the guest inspection data. *)
+and get_vmtype = function
+  | { i_type = "linux"; i_distro = "rhel"; i_major_version = major;
+      i_product_name = product }
+      when major >= 5 && string_find product "Server" >= 0 ->
+    `Server
+
+  | { i_type = "linux"; i_distro = "rhel"; i_major_version = major }
+      when major >= 5 ->
+    `Desktop
+
+  | { i_type = "linux"; i_distro = "rhel"; i_major_version = major;
+      i_product_name = product }
+      when major >= 3 && string_find product "ES" >= 0 ->
+    `Server
+
+  | { i_type = "linux"; i_distro = "rhel"; i_major_version = major;
+      i_product_name = product }
+      when major >= 3 && string_find product "AS" >= 0 ->
+    `Server
+
+  | { i_type = "linux"; i_distro = "rhel"; i_major_version = major }
+      when major >= 3 ->
+    `Desktop
+
+  | { i_type = "linux"; i_distro = "fedora" } -> `Desktop
+
+  | { i_type = "windows"; i_major_version = 5; i_minor_version = 1 } ->
+    `Desktop                            (* Windows XP *)
+
+  | { i_type = "windows"; i_major_version = 5; i_minor_version = 2;
+      i_product_name = product } when string_find product "XP" >= 0 ->
+    `Desktop                            (* Windows XP *)
+
+  | { i_type = "windows"; i_major_version = 5; i_minor_version = 2 } ->
+    `Server                            (* Windows 2003 *)
+
+  | { i_type = "windows"; i_major_version = 6; i_minor_version = 0;
+      i_product_name = product } when string_find product "Server" >= 0 ->
+    `Server                            (* Windows 2008 *)
+
+  | { i_type = "windows"; i_major_version = 6; i_minor_version = 0 } ->
+    `Desktop                            (* Vista *)
+
+  | { i_type = "windows"; i_major_version = 6; i_minor_version = 1;
+      i_product_name = product } when string_find product "Server" >= 0 ->
+    `Server                             (* Windows 2008R2 *)
+
+  | { i_type = "windows"; i_major_version = 6; i_minor_version = 1 } ->
+    `Server                             (* Windows 7 *)
+
+  | _ -> `Server
+
+and get_ostype = function
+  | { i_type = "linux"; i_distro = "rhel"; i_major_version = v;
+      i_arch = "i386" } ->
+    sprintf "RHEL%d" v
+
+  | { i_type = "linux"; i_distro = "rhel"; i_major_version = v;
+      i_arch = "x86_64" } ->
+    sprintf "RHEL%dx64" v
+
+  | { i_type = "linux" } -> "OtherLinux"
+
+  | { i_type = "windows"; i_major_version = 5; i_minor_version = 1 } ->
+    "WindowsXP" (* no architecture differentiation of XP on RHEV *)
+
+  | { i_type = "windows"; i_major_version = 5; i_minor_version = 2;
+      i_product_name = product } when string_find product "XP" >= 0 ->
+    "WindowsXP" (* no architecture differentiation of XP on RHEV *)
+
+  | { i_type = "windows"; i_major_version = 5; i_minor_version = 2;
+      i_arch = "i386" } ->
+    "Windows2003"
+
+  | { i_type = "windows"; i_major_version = 5; i_minor_version = 2;
+      i_arch = "x86_64" } ->
+    "Windows2003x64"
+
+  | { i_type = "windows"; i_major_version = 6; i_minor_version = 0;
+      i_arch = "i386" } ->
+    "Windows2008"
+
+  | { i_type = "windows"; i_major_version = 6; i_minor_version = 0;
+      i_arch = "x86_64" } ->
+    "Windows2008x64"
+
+  | { i_type = "windows"; i_major_version = 6; i_minor_version = 1;
+      i_arch = "i386" } ->
+    "Windows7"
+
+  | { i_type = "windows"; i_major_version = 6; i_minor_version = 1;
+      i_arch = "x86_64"; i_product_variant = "Client" } ->
+    "Windows7x64"
+
+  | { i_type = "windows"; i_major_version = 6; i_minor_version = 1;
+      i_arch = "x86_64" } ->
+    "Windows2008R2x64"
+
+  | { i_type = typ; i_distro = distro;
+      i_major_version = major; i_minor_version = minor;
+      i_product_name = product } ->
+    warning ~prog (f_"unknown guest operating system: %s %s %d.%d (%s)")
+      typ distro major minor product;
+    "Unassigned"
+
+(* This modifies the OVF DOM, adding a section for each disk. *)
+and add_disks output_alloc overlays guestcaps ovf =
+  let references =
+    let nodes = path_to_nodes ovf ["ovf:Envelope"; "References"] in
+    match nodes with
+    | [] | _::_::_ -> assert false
+    | [node] -> node in
+  let disk_section =
+    let sections = path_to_nodes ovf ["ovf:Envelope"; "Section"] in
+    try find_node_by_attr sections ("xsi:type", "ovf:DiskSection_Type")
+    with Not_found -> assert false in
+  let virtualhardware_section =
+    let sections = path_to_nodes ovf ["ovf:Envelope"; "Content"; "Section"] in
+    try find_node_by_attr sections ("xsi:type", "ovf:VirtualHardwareSection_Type")
+    with Not_found -> assert false in
+
+  let append_child child = function
+    | PCData _ -> assert false
+    | Element e -> e.e_children <- e.e_children @ [child]
+  in
+
+  (* Iterate over the disks, adding them to the OVF document. *)
+  iteri (
+    fun i ov ->
+      let is_boot_drive = i == 0 in
+
+      let target_file = ov.ov_target_file
+      and vol_uuid = ov.ov_vol_uuid in
+      assert (vol_uuid <> "");
+
+      let fileref = !image_uuid // vol_uuid in
+
+      let size_gb =
+        Int64.to_float ov.ov_virtual_size /. 1024. /. 1024. /. 1024. in
+      let usage_gb =
+        let usage_mb = du_m target_file in
+        Int64.to_float usage_mb /. 1024. in
+
+      let format_for_rhev =
+        match ov.ov_target_format with
+        | "raw" -> "RAW"
+        | "qcow2" -> "COW"
+        | _ ->
+          error (f_"RHEV does not support the output format '%s', only raw or qcow2") ov.ov_target_format in
+
+      let output_alloc_for_rhev =
+        match output_alloc with
+        | `Sparse -> "SPARSE"
+        | `Preallocated -> "PREALLOCATED" in
+
+      (* Add disk to <References/> node. *)
+      let disk =
+        e "File" [
+          "ovf:href", fileref;
+          "ovf:id", vol_uuid;
+          "ovf:size", Int64.to_string ov.ov_virtual_size;
+          "ovf:description", title;
+        ] [] in
+      append_child disk references;
+
+      (* Add disk to DiskSection. *)
+      let disk =
+        e "Disk" [
+          "ovf:diskId", vol_uuid;
+          "ovf:size", sprintf "%.1f" size_gb;
+          "ovf:actual_size", sprintf "%.1f" usage_gb;
+          "ovf:fileRef", fileref;
+          "ovf:parentRef", "";
+          "ovf:vm_snapshot_id", uuidgen ~prog ();
+          "ovf:volume-format", format_for_rhev;
+          "ovf:volume-type", output_alloc_for_rhev;
+          "ovf:format", "http://en.wikipedia.org/wiki/Byte"; (* wtf? *)
+          "ovf:disk-interface",
+            if guestcaps.gcaps_block_bus = "virtio" then "VirtIO" else "IDE";
+          "ovf:disk-type", "System"; (* RHBZ#744538 *)
+          "ovf:boot", if is_boot_drive then "True" else "False";
+        ] [] in
+      append_child disk disk_section;
+
+      (* Add disk to VirtualHardware. *)
+      let item =
+        e "Item" [] [
+          e "rasd:InstanceId" [] [PCData vol_uuid];
+          e "rasd:ResourceType" [] [PCData "17"];
+          e "rasd:HostResource" [] [PCData fileref];
+          e "rasd:Parent" [] [PCData "00000000-0000-0000-0000-000000000000"];
+          e "rasd:Template" [] [PCData "00000000-0000-0000-0000-000000000000"];
+          e "rasd:ApplicationList" [] [];
+          e "rasd:StorageId" [] [PCData !esd.uuid];
+          e "rasd:StoragePoolId" [] [PCData "00000000-0000-0000-0000-000000000000"];
+          e "rasd:CreationDate" [] [PCData iso_time];
+          e "rasd:LastModified" [] [PCData iso_time];
+          e "rasd:last_modified_date" [] [PCData iso_time];
+        ] in
+      append_child item virtualhardware_section;
+  ) overlays
+
+and du_m filename =
+  (* There's no OCaml binding for st_blocks, so run coreutils 'du -m'
+   * to get the used size in megabytes.
+   *)
+  let cmd = sprintf "du -m %s | awk '{print $1}'" (quote filename) in
+  let lines = external_command ~prog cmd in
+  (* We really don't want the metadata generation to fail because
+   * of some silly usage information, so ignore errors here.
+   *)
+  match lines with
+  | line::_ -> (try Int64.of_string line with _ -> 0L)
+  | [] -> 0L
diff --git a/v2v/target_RHEV.mli b/v2v/target_RHEV.mli
new file mode 100644
index 0000000..6cc5c76
--- /dev/null
+++ b/v2v/target_RHEV.mli
@@ -0,0 +1,24 @@
+(* 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.
+ *)
+
+val initialize : verbose:bool -> string -> Types.source ->
+  [`Sparse | `Preallocated] -> Types.overlay list -> Types.overlay list
+
+val create_metadata : string -> [`Server | `Desktop] option -> Types.source ->
+  [`Sparse | `Preallocated] -> Types.overlay list -> Types.inspect ->
+  Types.guestcaps -> unit
diff --git a/v2v/types.ml b/v2v/types.ml
index 849ad0f..05aafb1 100644
--- a/v2v/types.ml
+++ b/v2v/types.ml
@@ -27,7 +27,7 @@ type input =
 type output =
 | OutputLibvirt of string option
 | OutputLocal of string
-| OutputRHEV of string
+| OutputRHEV of string * [`Server|`Desktop] option
 
 type source = {
   s_dom_type : string;
@@ -83,6 +83,7 @@ type overlay = {
   ov_preallocation : string option;
   ov_source_file : string;
   ov_source_format : string option;
+  ov_vol_uuid : string;
 }
 
 let string_of_overlay ov =
@@ -96,6 +97,7 @@ ov_virtual_size = %Ld
 ov_preallocation = %s
 ov_source_file = %s
 ov_source_format = %s
+ov_vol_uuid = %s
 "
     ov.ov_overlay
     ov.ov_target_file ov.ov_target_file_tmp
@@ -105,6 +107,7 @@ ov_source_format = %s
     (match ov.ov_preallocation with None -> "None" | Some s -> s)
     ov.ov_source_file
     (match ov.ov_source_format with None -> "None" | Some s -> s)
+    ov.ov_vol_uuid
 
 type inspect = {
   i_root : string;
diff --git a/v2v/types.mli b/v2v/types.mli
index c8ef5c3..0415a27 100644
--- a/v2v/types.mli
+++ b/v2v/types.mli
@@ -26,7 +26,7 @@ type input =
 type output =
 | OutputLibvirt of string option    (* -o libvirt: -oc *)
 | OutputLocal of string             (* -o local: directory *)
-| OutputRHEV of string              (* -o rhev: output storage *)
+| OutputRHEV of string * [`Server|`Desktop] option (* -o rhev: output storage *)
 (** The output arguments as specified on the command line. *)
 
 type source = {
@@ -62,8 +62,11 @@ type overlay = {
   (* Note: the next two fields are for information only and must not
    * be opened/copied/etc.
    *)
-  ov_source_file : string;   (** qemu URI for source file. *)
+  ov_source_file : string;          (** qemu URI for source file. *)
   ov_source_format : string option; (** Source file format, if known. *)
+
+  (* Only used by RHEV.  XXX Should be parameterized type. *)
+  ov_vol_uuid : string;                 (** RHEV volume UUID *)
 }
 (** Disk overlays and destination disks. *)
 
diff --git a/v2v/utils.ml b/v2v/utils.ml
index b58c18d..f065a66 100644
--- a/v2v/utils.ml
+++ b/v2v/utils.ml
@@ -40,6 +40,12 @@ let xml_quote_attr str =
   let str = Common_utils.replace_str str ">" ">" in
   str
 
+let xml_quote_pcdata str =
+  let str = Common_utils.replace_str str "&" "&" in
+  let str = Common_utils.replace_str str "<" "<" in
+  let str = Common_utils.replace_str str ">" ">" in
+  str
+
 external drive_name : int -> string = "v2v_utils_drive_name"
 
 let compare_app2_versions app1 app2 =
diff --git a/v2v/v2v.ml b/v2v/v2v.ml
index 4dca2fe..6f6ee93 100644
--- a/v2v/v2v.ml
+++ b/v2v/v2v.ml
@@ -93,7 +93,7 @@ let rec main () =
    * work.
    *)
   let overlays =
-    initialize_target g
+    initialize_target ~verbose g
       source output output_alloc output_format output_name overlays in
 
   (* Inspection - this also mounts up the filesystems. *)
@@ -197,7 +197,9 @@ let rec main () =
     | OutputLibvirt oc -> assert false
     | OutputLocal dir ->
       Target_local.create_metadata dir renamed_source overlays guestcaps
-    | OutputRHEV os -> assert false in
+    | OutputRHEV (os, vmtype) ->
+      Target_RHEV.create_metadata os vmtype renamed_source output_alloc
+        overlays inspect guestcaps in
 
   (* If we wrote to a temporary file, rename to the real file. *)
   List.iter (
@@ -213,7 +215,7 @@ let rec main () =
   if debug_gc then
     Gc.compact ()
 
-and initialize_target g
+and initialize_target ~verbose g
     source output output_alloc output_format output_name overlays =
   let overlays =
     mapi (
@@ -244,7 +246,8 @@ and initialize_target g
           ov_target_file = ""; ov_target_file_tmp = "";
           ov_target_format = format;
           ov_sd = sd; ov_virtual_size = vsize; ov_preallocation = preallocation;
-          ov_source_file = qemu_uri; ov_source_format = backing_format; }
+          ov_source_file = qemu_uri; ov_source_format = backing_format;
+          ov_vol_uuid = "" }
     ) overlays in
   let overlays =
     let renamed_source =
@@ -254,7 +257,8 @@ and initialize_target g
     match output with
     | OutputLibvirt oc -> assert false
     | OutputLocal dir -> Target_local.initialize dir renamed_source overlays
-    | OutputRHEV os -> assert false in
+    | OutputRHEV (os, _) ->
+      Target_RHEV.initialize ~verbose os renamed_source output_alloc overlays in
   overlays
 
 and inspect_source g root_choice =
diff --git a/v2v/virt-v2v.pod b/v2v/virt-v2v.pod
index 48fa870..084e4b6 100644
--- a/v2v/virt-v2v.pod
+++ b/v2v/virt-v2v.pod
@@ -132,14 +132,22 @@ For I<-o libvirt>, this is a libvirt pool (see S<C<virsh pool-list>>).
 
 For I<-o local>, this is a directory name.  The directory must exist.
 
-For I<-o rhev>, this is an NFS path of the form
-C<E<lt>hostE<gt>:E<lt>pathE<gt>>, eg:
+For I<-o rhev>, this can be an NFS path of the Export Storage Domain
+of the form C<E<lt>hostE<gt>:E<lt>pathE<gt>>, eg:
 
  rhev-storage.example.com:/rhev/export
 
 The NFS export must be mountable and writable by the user and host
 running virt-v2v, since the virt-v2v program has to actually mount it
-when it runs.
+when it runs.  So you probably have to run virt-v2v as C<root>.
+
+B<Or:> You can mount the Export Storage Domain yourself, and point
+I<-os> to the mountpoint.  Note that virt-v2v will still need to write
+to this remote directory, so virt-v2v will still need to run as
+C<root>.
+
+You will get an error if virt-v2v is unable to mount/write to the
+Export Storage Domain.
 
 =item B<-q>
 
@@ -202,6 +210,13 @@ Enable verbose messages for debugging.
 
 Display version number and exit.
 
+=item B<--vmtype> server|desktop
+
+For the RHEV target only, specify the type of guest.  You can set this
+to C<server> or C<desktop>.  If the option is not given, then a
+suitable default is chosen based on the detected guest operating
+system.
+
 =item B<-x>
 
 Enable tracing of libguestfs API calls.

-- 
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/pkg-libvirt/libguestfs.git



More information about the Pkg-libvirt-commits mailing list