[Pkg-libvirt-commits] [libguestfs] 21/40: v2v: Split off -o vdsm mode from -o rhev (RHBZ#1140156).

Hilko Bengen bengen at moszumanska.debian.org
Fri Oct 3 14:44:44 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.44-1
in repository libguestfs.

commit b67da2524fd8a95e217db8ef764ad1ca34b91a82
Author: Richard W.M. Jones <rjones at redhat.com>
Date:   Thu Sep 11 16:55:15 2014 +0100

    v2v: Split off -o vdsm mode from -o rhev (RHBZ#1140156).
    
    -o rhev: This is "traditional" output to a RHEV-M Export Storage Domain.
    It requires no cooperation from RHEV itself, and in all other respects
    works the same way as old virt-v2v did.
    
    -o vdsm: This is the new mode which requires VDSM cooperation.  VDSM
    manages the conversion, creating the directories, suggesting UUIDs,
    and cleaning up on failure.  This allows direct import into a Data
    Domain.
    
    The --rhev* command arguments now only apply to -o vdsm, and therefore
    have been renamed to --vdsm*
    
    The --vmtype command line argument may be used with -o rhev or -o vdsm.
---
 po/POTFILES-ml                                     |   1 +
 v2v/Makefile.am                                    |   4 +-
 v2v/cmdline.ml                                     |  52 +-
 v2v/lib_ovf.ml                                     | 343 +++++++++++-
 v2v/lib_ovf.mli                                    |  17 +-
 v2v/output_rhev.ml                                 | 614 +++++----------------
 v2v/output_rhev.mli                                |  15 +-
 v2v/output_vdsm.ml                                 | 156 ++++++
 v2v/{output_rhev.mli => output_vdsm.mli}           |  19 +-
 ...-rhev-options.sh => test-v2v-o-vdsm-options.sh} |  18 +-
 v2v/virt-v2v.pod                                   | 103 ++--
 11 files changed, 735 insertions(+), 607 deletions(-)

diff --git a/po/POTFILES-ml b/po/POTFILES-ml
index fab73ee..8a169ab 100644
--- a/po/POTFILES-ml
+++ b/po/POTFILES-ml
@@ -101,6 +101,7 @@ v2v/output_glance.ml
 v2v/output_libvirt.ml
 v2v/output_local.ml
 v2v/output_rhev.ml
+v2v/output_vdsm.ml
 v2v/stringMap.ml
 v2v/types.ml
 v2v/utils.ml
diff --git a/v2v/Makefile.am b/v2v/Makefile.am
index 3e84f3c..5447877 100644
--- a/v2v/Makefile.am
+++ b/v2v/Makefile.am
@@ -46,6 +46,7 @@ SOURCES_MLI = \
 	output_libvirt.mli \
 	output_local.mli \
 	output_rhev.mli \
+	output_vdsm.mli \
 	types.mli \
 	xml.mli
 
@@ -72,6 +73,7 @@ SOURCES_ML = \
 	output_libvirt.ml \
 	output_local.ml \
 	output_rhev.ml \
+	output_vdsm.ml \
 	cmdline.ml \
 	v2v.ml
 
@@ -212,7 +214,7 @@ TESTS = \
 	test-v2v-o-glance.sh \
 	test-v2v-o-libvirt.sh \
 	test-v2v-o-rhev.sh \
-	test-v2v-o-rhev-options.sh \
+	test-v2v-o-vdsm-options.sh \
 	test-v2v-of-option.sh \
 	test-v2v-on-option.sh \
 	test-v2v-print-source.sh \
diff --git a/v2v/cmdline.ml b/v2v/cmdline.ml
index 2e7a1ca..0c7c8bd 100644
--- a/v2v/cmdline.ml
+++ b/v2v/cmdline.ml
@@ -43,8 +43,8 @@ let parse_cmdline () =
   let output_storage = ref "" in
   let print_source = ref false in
   let quiet = ref false in
-  let rhev_image_uuid = ref "" in
-  let rhev_vm_uuid = ref "" in
+  let vdsm_image_uuid = ref "" in
+  let vdsm_vm_uuid = ref "" in
   let verbose = ref false in
   let trace = ref false in
   let vmtype = ref "" in
@@ -84,6 +84,7 @@ let parse_cmdline () =
     | "libvirt" -> output_mode := `Libvirt
     | "disk" | "local" -> output_mode := `Local
     | "ovirt" | "rhev" -> output_mode := `RHEV
+    | "vdsm" -> output_mode := `VDSM
     | s ->
       error (f_"unknown -o option: %s") s
   in
@@ -106,8 +107,8 @@ let parse_cmdline () =
       error (f_"unknown --root option: %s") s
   in
 
-  let rhev_vol_uuids = ref [] in
-  let add_rhev_vol_uuid s = rhev_vol_uuids := s :: !rhev_vol_uuids in
+  let vdsm_vol_uuids = ref [] in
+  let add_vdsm_vol_uuid s = vdsm_vol_uuids := s :: !vdsm_vol_uuids in
 
   let i_options =
     String.concat "|" (Modules_list.input_modules ())
@@ -135,13 +136,13 @@ let parse_cmdline () =
     "--print-source", Arg.Set print_source, " " ^ s_"Print source and stop";
     "-q",        Arg.Set quiet,             " " ^ s_"Quiet output";
     "--quiet",   Arg.Set quiet,             ditto;
-    "--rhev-image-uuid",
-                 Arg.Set_string rhev_image_uuid, "uuid " ^ s_"Output image UUID";
-    "--rhev-vol-uuid",
-                 Arg.String add_rhev_vol_uuid, "uuid " ^ s_"Output vol UUID(s)";
-    "--rhev-vm-uuid",
-                 Arg.Set_string rhev_vm_uuid, "uuid " ^ s_"Output VM UUID";
     "--root",    Arg.String set_root_choice,"ask|... " ^ s_"How to choose root filesystem";
+    "--vdsm-image-uuid",
+                 Arg.Set_string vdsm_image_uuid, "uuid " ^ s_"Output image UUID";
+    "--vdsm-vol-uuid",
+                 Arg.String add_vdsm_vol_uuid, "uuid " ^ s_"Output vol UUID(s)";
+    "--vdsm-vm-uuid",
+                 Arg.Set_string vdsm_vm_uuid, "uuid " ^ s_"Output VM UUID";
     "-v",        Arg.Set verbose,           " " ^ s_"Enable debugging messages";
     "--verbose", Arg.Set verbose,           ditto;
     "-V",        Arg.Unit display_version,  " " ^ s_"Display version and exit";
@@ -193,10 +194,10 @@ read the man page virt-v2v(1).
   let output_storage = !output_storage in
   let print_source = !print_source in
   let quiet = !quiet in
-  let rhev_image_uuid = match !rhev_image_uuid with "" -> None | s -> Some s in
-  let rhev_vol_uuids = List.rev !rhev_vol_uuids in
-  let rhev_vm_uuid = match !rhev_vm_uuid with "" -> None | s -> Some s in
   let root_choice = !root_choice in
+  let vdsm_image_uuid = !vdsm_image_uuid in
+  let vdsm_vol_uuids = List.rev !vdsm_vol_uuids in
+  let vdsm_vm_uuid = !vdsm_vm_uuid in
   let verbose = !verbose in
   let trace = !trace in
   let vmtype =
@@ -270,7 +271,7 @@ read the man page virt-v2v(1).
       if output_storage <> "" then
         error (f_"-o glance: -os option cannot be used in this output mode");
       if vmtype <> None then
-        error (f_"--vmtype option can only be used with '-o rhev'");
+        error (f_"--vmtype option cannot be used with '-o glance'");
       if not do_copy then
         error (f_"--no-copy and '-o glance' cannot be used at the same time");
       Output_glance.output_glance verbose
@@ -280,7 +281,7 @@ read the man page virt-v2v(1).
       let output_storage =
         if output_storage = "" then "default" else output_storage in
       if vmtype <> None then
-        error (f_"--vmtype option can only be used with '-o rhev'");
+        error (f_"--vmtype option cannot be used with '-o libvirt'");
       if not do_copy then
         error (f_"--no-copy and '-o libvirt' cannot be used at the same time");
       Output_libvirt.output_libvirt verbose output_conn output_storage
@@ -292,19 +293,26 @@ read the man page virt-v2v(1).
         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'");
+        error (f_"--vmtype option cannot be used with '-o local'");
       Output_local.output_local verbose output_storage
 
     | `RHEV ->
       if output_storage = "" then
         error (f_"-o rhev: output storage was not specified, use '-os'");
-      let rhev_params = {
-        Output_rhev.image_uuid = rhev_image_uuid;
-        vol_uuids = rhev_vol_uuids;
-        vm_uuid = rhev_vm_uuid;
-        vmtype = vmtype;
+      Output_rhev.output_rhev verbose output_storage vmtype output_alloc
+
+    | `VDSM ->
+      if output_storage = "" then
+        error (f_"-o vdsm: output storage was not specified, use '-os'");
+      if vdsm_image_uuid = "" || vdsm_vm_uuid = "" then
+        error (f_"-o vdsm: either --vdsm-image-uuid or --vdsm-vm-uuid was not specified");
+      let vdsm_params = {
+        Output_vdsm.image_uuid = vdsm_image_uuid;
+        vol_uuids = vdsm_vol_uuids;
+        vm_uuid = vdsm_vm_uuid;
       } in
-      Output_rhev.output_rhev verbose output_storage rhev_params output_alloc in
+      Output_vdsm.output_vdsm verbose output_storage vdsm_params
+        vmtype output_alloc in
 
   input, output,
   debug_gc, do_copy, network_map,
diff --git a/v2v/lib_ovf.ml b/v2v/lib_ovf.ml
index e7c98c0..5203d86 100644
--- a/v2v/lib_ovf.ml
+++ b/v2v/lib_ovf.ml
@@ -21,15 +21,29 @@
 open Common_gettext.Gettext
 open Common_utils
 
+open Unix
+open Printf
+
 open Types
 open Utils
+open DOM
 
-open Printf
+let title = sprintf "Exported by virt-v2v %s" Config.package_version
+
+(* 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
 
 (* Guess vmtype based on the guest inspection data.  This is used
  * when the [--vmtype] parameter is NOT passed.
  *)
-let ovf_vmtype = function
+let 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 ->
@@ -82,7 +96,7 @@ let ovf_vmtype = function
   | _ -> `Server
 
 (* Determine the ovf:OperatingSystemSection_Type from libguestfs inspection. *)
-and ovf_ostype = function
+and get_ostype = function
   | { i_type = "linux"; i_distro = "rhel"; i_major_version = v;
       i_arch = "i386" } ->
     sprintf "RHEL%d" v
@@ -134,3 +148,326 @@ and ovf_ostype = function
     warning ~prog (f_"unknown guest operating system: %s %s %d.%d (%s)")
       typ distro major minor product;
     "Unassigned"
+
+(* Generate the .meta file associated with each volume. *)
+let create_meta_files verbose output_alloc sd_uuid image_uuid targets =
+  let output_alloc_for_rhev =
+    match output_alloc with
+    | `Sparse -> "SPARSE"
+    | `Preallocated -> "PREALLOCATED" in
+
+  List.iter (
+    fun ({ target_overlay = ov; target_file = target_file } as t) ->
+      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.s_qemu_uri
+            ov.ov_virtual_size;
+        ov.ov_virtual_size /^ 512L in
+
+      let format_for_rhev =
+        match t.target_format with
+        | "raw" -> "RAW"
+        | "qcow2" -> "COW"
+        | _ ->
+          error (f_"RHEV does not support the output format '%s', only raw or qcow2") t.target_format in
+
+      let chan = open_out vol_meta in
+      let fpf fs = fprintf chan fs in
+      fpf "DOMAIN=%s\n" sd_uuid; (* "Domain" as in 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;
+  ) targets
+
+(* Create the OVF file. *)
+let rec create_ovf verbose source targets guestcaps inspect
+    output_alloc vmtype sd_uuid image_uuid vol_uuids vm_uuid =
+  assert (List.length targets = List.length vol_uuids);
+
+  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 targets guestcaps output_alloc sd_uuid image_uuid vol_uuids ovf;
+
+  (* Old virt-v2v ignored removable media. XXX *)
+
+  (* Add networks to the OVF XML. *)
+  add_networks source.s_nics guestcaps ovf;
+
+  (* Old virt-v2v didn't really look at the video and display
+   * metadata, instead just adding a single standard display (see
+   * above).  However it did warn if there was a password on the
+   * display of the old guest.
+   *)
+  (match source with
+  | { s_display = Some { s_password = Some _ } } ->
+    warning ~prog (f_"This guest required a password for connection to its display, but this is not supported by RHEV.  Therefore the converted guest's display will not require a separate password to connect.");
+    | _ -> ());
+
+  (* Return the OVF document. *)
+  ovf
+
+and append_child child = function
+  | PCData _ | Comment _ -> assert false
+  | Element e -> e.e_children <- e.e_children @ [child]
+
+(* This modifies the OVF DOM, adding a section for each disk. *)
+and add_disks targets guestcaps output_alloc sd_uuid image_uuid vol_uuids 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
+
+  (* Iterate over the disks, adding them to the OVF document. *)
+  iteri (
+    fun i ({ target_overlay = ov } as t, vol_uuid) ->
+      let is_boot_drive = i == 0 in
+
+      let fileref = image_uuid // vol_uuid in
+
+      let size_gb =
+        Int64.to_float ov.ov_virtual_size /. 1024. /. 1024. /. 1024. in
+      let usage_gb =
+        (* In the --no-copy case it can happen that the target file
+         * does not exist.  In that case we simply omit the
+         * ovf:actual_size attribute.
+         *)
+        if Sys.file_exists t.target_file then (
+          let usage_mb = du_m t.target_file in
+          if usage_mb > 0L then (
+            let usage_mb = Int64.to_float usage_mb /. 1024. in
+            Some usage_mb
+          ) else None
+        ) else None in
+
+      let format_for_rhev =
+        match t.target_format with
+        | "raw" -> "RAW"
+        | "qcow2" -> "COW"
+        | _ ->
+          error (f_"RHEV does not support the output format '%s', only raw or qcow2") t.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 =
+        let attrs = [
+          "ovf:diskId", vol_uuid;
+          "ovf:size", sprintf "%.1f" size_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",
+          (match guestcaps.gcaps_block_bus with
+          | Virtio_blk -> "VirtIO" | IDE -> "IDE");
+          "ovf:disk-type", "System"; (* RHBZ#744538 *)
+          "ovf:boot", if is_boot_drive then "True" else "False";
+        ] in
+        let attrs =
+          match usage_gb with
+          | None -> attrs
+          | Some usage_gb ->
+            ("ovf:actual_size", sprintf "%.1f" usage_gb) :: attrs in
+        e "Disk" attrs [] 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 sd_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;
+  ) (List.combine targets vol_uuids)
+
+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
+
+(* This modifies the OVF DOM, adding a section for each NIC. *)
+and add_networks nics guestcaps ovf =
+  let network_section =
+    let sections = path_to_nodes ovf ["ovf:Envelope"; "Section"] in
+    try find_node_by_attr sections ("xsi:type", "ovf:NetworkSection_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
+
+  (* Iterate over the NICs, adding them to the OVF document. *)
+  iteri (
+    fun i { s_mac = mac; s_vnet_type = vnet_type;
+            s_vnet = vnet; s_vnet_orig = vnet_orig } ->
+      let dev = sprintf "eth%d" i in
+
+      let model =
+        match guestcaps.gcaps_net_bus with
+        | RTL8139 -> "1"
+        | E1000 -> "2"
+        | Virtio_net -> "3"
+      (*| bus ->
+        warning ~prog (f_"unknown NIC model %s for ethernet device %s.  This NIC will be imported as rtl8139 instead.")
+        bus dev;
+        "1" *) in
+
+      if vnet_orig <> vnet then (
+        let c =
+          Comment (sprintf "mapped from \"%s\" to \"%s\"" vnet_orig vnet) in
+        append_child c network_section
+      );
+
+      let network = e "Network" ["ovf:name", vnet] [] in
+      append_child network network_section;
+
+      let item =
+        let children = [
+          e "rasd:InstanceId" [] [PCData "3"];
+          e "rasd:Caption" [] [PCData (sprintf "Ethernet adapter on %s" vnet)];
+          e "rasd:ResourceType" [] [PCData "10"];
+          e "rasd:ResourceSubType" [] [PCData model];
+          e "rasd:Connection" [] [PCData vnet];
+          e "rasd:Name" [] [PCData dev];
+        ] in
+        let children =
+          match mac with
+          | None -> children
+          | Some mac -> children @ [e "rasd:MACAddress" [] [PCData mac]] in
+        e "Item" [] children in
+      append_child item virtualhardware_section;
+  ) nics
diff --git a/v2v/lib_ovf.mli b/v2v/lib_ovf.mli
index 59eced7..b3d185b 100644
--- a/v2v/lib_ovf.mli
+++ b/v2v/lib_ovf.mli
@@ -18,17 +18,8 @@
 
 (** Functions for dealing with OVF files. *)
 
-open Common_gettext.Gettext
-open Common_utils
+val create_meta_files : bool -> [`Sparse|`Preallocated] -> string -> string -> Types.target list -> unit
+(** Generate the .meta file associated with each volume. *)
 
-open Utils
-
-open Printf
-
-val ovf_vmtype : Types.inspect -> [`Server|`Desktop]
-(** Guess vmtype based on the guest inspection data.  This is used
-    when the [--vmtype] parameter is NOT passed. *)
-
-val ovf_ostype : Types.inspect -> string
-(** Determine the ovf:OperatingSystemSection_Type from libguestfs
-    inspection. *)
+val create_ovf : bool -> Types.source -> Types.target list -> Types.guestcaps -> Types.inspect -> [`Sparse|`Preallocated] -> [`Server|`Desktop] option -> string -> string -> string list -> string -> DOM.doc
+(** Create the OVF file. *)
diff --git a/v2v/output_rhev.ml b/v2v/output_rhev.ml
index 854ff20..84157de 100644
--- a/v2v/output_rhev.ml
+++ b/v2v/output_rhev.ml
@@ -26,48 +26,98 @@ open Types
 open Utils
 open DOM
 
-let title = sprintf "Exported by virt-v2v %s" Config.package_version
-
-type rhev_params = {
-  image_uuid : string option;
-  vol_uuids : string list;
-  vm_uuid : string option;
-  vmtype : [`Server|`Desktop] option;
-}
-
-(* Describes a mounted Export Storage Domain. *)
-type export_storage_domain = {
-  mp : string;                          (* Local mountpoint. *)
-  uuid : string;                        (* /mp/uuid *)
-}
-
-let append_child child = function
-  | PCData _ | Comment _ -> assert false
-  | Element e -> e.e_children <- e.e_children @ [child]
-
-(* 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
-
-class output_rhev verbose os rhev_params output_alloc =
+let rec mount_and_check_storage_domain verbose domain_class 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_storage_domain verbose domain_class 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 %s path [-os %s], or else you need to rerun virt-v2v as root.") domain_class 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_storage_domain verbose domain_class os mp
+
+and check_storage_domain verbose domain_class os mp =
+  (* Typical SD 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 %s specified by the '-os %s' parameter on the command line.  Is it really an OVirt or RHEV-M %s?  The original error is: %s") domain_class os domain_class 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 %s (%s).  Is it really an OVirt or RHEV-M %s?") domain_class os domain_class
+    | _::_ ->
+      error (f_"there are multiple UUIDs in the %s (%s).  This is unexpected, and may be a bug in virt-v2v or OVirt.") domain_class os in
+
+  (* Check that the domain has been attached to a Data Center by
+   * checking that the master/vms directory exists.
+   *)
+  let () =
+    let master_vms_dir = mp // uuid // "master" // "vms" in
+    if not (is_directory master_vms_dir) then
+      error (f_"%s does not exist or is not a directory.\n\nMost likely cause: Either the %s (%s) has not been attached to any Data Center, or the path %s is not an %s at all.\n\nYou have to attach the %s to a Data Center using the RHEV-M / OVirt user interface first.\n\nIf you don't know what the %s mount point should be then you can also find this out through the RHEV-M user interface.")
+        master_vms_dir domain_class os os
+        domain_class domain_class domain_class in
+
+  (* Check that the SD is writable. *)
+  let testfile = mp // uuid // "v2v-write-test" in
+  let write_test_failed err =
+    error (f_"the %s (%s) is not writable.\n\nThis probably means you need to run virt-v2v as 'root'.\n\nOriginal error was: %s")
+      domain_class 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 SD mountpoint and UUID. *)
+  (mp, uuid)
+
+class output_rhev verbose os vmtype output_alloc =
 object
   inherit output verbose
 
   method as_options =
-    sprintf "-o rhev -os %s%s%s%s%s" os
-      (match rhev_params.image_uuid with
-      | None -> "" | Some uuid -> sprintf " --rhev-image-uuid %s" uuid)
-      (String.concat ""
-         (List.map (sprintf " --rhev-vol-uuid %s") rhev_params.vol_uuids))
-      (match rhev_params.vm_uuid with
-      | None -> "" | Some uuid -> sprintf " --rhev-vm-uuid %s" uuid)
-      (match rhev_params.vmtype with
+    sprintf "-o rhev -os %s%s" os
+      (match vmtype with
       | None -> ""
       | Some `Server -> " --vmtype server"
       | Some `Desktop -> " --vmtype desktop")
@@ -77,8 +127,9 @@ object
    *)
   method keep_serial_console = false
 
-  (* Export Storage Domain mountpoint. *)
-  val mutable esd = { mp = ""; uuid = "" }
+  (* Export Storage Domain mountpoint and UUID. *)
+  val mutable esd_mp = ""
+  val mutable esd_uuid = ""
 
   (* Target image directory, UUID. *)
   val mutable image_dir = ""
@@ -87,8 +138,10 @@ object
   (* Target VM UUID. *)
   val mutable vm_uuid = ""
 
-  (* Map overlay to volume UUID.  Key is [ov_sd] field which is unique. *)
-  val vol_uuid = Hashtbl.create 13
+  (* Volume UUIDs.  The length of this list will be the same as the
+   * list of targets.
+   *)
+  val mutable vol_uuids = []
 
   (* Flag to indicate if the target image (image_dir) should be
    * deleted.  This is set to false once we know the conversion was
@@ -110,136 +163,28 @@ object
    * displaying errors there.
    *)
   method prepare_targets _ targets =
-    let rec mount_and_check_export_storage_domain () =
-      (* 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.
-       *)
-      let () =
-        let master_vms_dir = mp // uuid // "master" // "vms" in
-        if not (is_directory master_vms_dir) then
-          error (f_"%s does not exist or is not a directory.\n\nMost likely cause: Either the Export Storage Domain (%s) has not been attached to any Data Center, or the path %s is not an Export Storage Domain at all.\n\nYou have to attach the Export Storage Domain to a Data Center using the RHEV-M / OVirt user interface first.\n\nIf you don't know what the Export Storage Domain mount point should be then you can also find this out through the RHEV-M user interface.")
-            master_vms_dir os os in
-
-      (* 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 }
-    in
-
-    (* Create unique UUIDs for everything, either based on the command
-     * line parameters or else we invent them here.
-     *)
-    let create_uuids () =
-      image_uuid <-
-        (match rhev_params.image_uuid with
-        | Some uuid -> uuid
-        | None -> uuidgen ~prog ());
-      vm_uuid <-
-        (match rhev_params.vm_uuid with
-        | Some uuid -> uuid
-        | None -> uuidgen ~prog ());
-
-      (match rhev_params.vol_uuids with
-      | [] ->
-        (* Generate random volume UUIDs for each target. *)
-        List.iter (
-          fun t ->
-            let uuid = uuidgen ~prog () in
-            Hashtbl.replace vol_uuid t.target_overlay.ov_sd uuid
-        ) targets
-      | uuids ->
-        (* Use the volume UUIDs passed to us on the command line. *)
-        try
-          List.iter (
-            fun (t, uuid) ->
-              Hashtbl.replace vol_uuid t.target_overlay.ov_sd uuid
-          ) (List.combine targets uuids)
-        with Invalid_argument _ ->
-          error (f_"the number of '--rhev-vol-uuid' parameters passed on the command line has to match the number of guest disk images (for this guest: %d)")
-            (List.length targets)
-      )
-    in
-
-    esd <- mount_and_check_export_storage_domain ();
+    let mp, uuid =
+      mount_and_check_storage_domain verbose (s_"Export Storage Domain") os in
+    esd_mp <- mp;
+    esd_uuid <- uuid;
     if verbose then
       eprintf "RHEV: ESD mountpoint: %s\nRHEV: ESD UUID: %s\n%!"
-        esd.mp esd.uuid;
+        esd_mp esd_uuid;
 
-    create_uuids ();
+    (* Create unique UUIDs for everything *)
+    image_uuid <- uuidgen ~prog ();
+    vm_uuid <- uuidgen ~prog ();
+    (* Generate random volume UUIDs for each target. *)
+    vol_uuids <-
+      List.map (
+        fun _ -> uuidgen ~prog ()
+      ) targets;
 
     (* 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.
      *)
-    image_dir <- esd.mp // esd.uuid // "images" // image_uuid;
+    image_dir <- esd_mp // esd_uuid // "images" // image_uuid;
     mkdir image_dir 0o755;
     at_exit (fun () ->
       if delete_target_directory then (
@@ -248,12 +193,9 @@ object
       )
     );
     if verbose then
-      eprintf "RHEV: export directory: %s\n%!" image_dir;
+      eprintf "RHEV: image 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:
+    (* The final directory structure should look 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
@@ -262,344 +204,36 @@ object
      *      <VOL_UUID_3>        # etc
      *      <VOL_UUID_3>.meta   #
      *)
-    let targets =
-      let output_alloc_for_rhev =
-        match output_alloc with
-        | `Sparse -> "SPARSE"
-        | `Preallocated -> "PREALLOCATED" in
 
+    (* Generate the randomly named target files (just the names).
+     * The main code is what generates the files themselves.
+     *)
+    let targets =
       List.map (
-        fun ({ target_overlay = ov } as t) ->
+        fun ({ target_overlay = ov } as t, vol_uuid) ->
           let ov_sd = ov.ov_sd in
-          let vol_uuid =
-            try Hashtbl.find vol_uuid ov_sd
-            with Not_found -> assert false in
           let target_file = image_dir // vol_uuid in
 
           if verbose then
             eprintf "RHEV: will export %s to %s\n%!" 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.s_qemu_uri
-                ov.ov_virtual_size;
-            ov.ov_virtual_size /^ 512L in
-
-          let format_for_rhev =
-            match t.target_format with
-            | "raw" -> "RAW"
-            | "qcow2" -> "COW"
-            | _ ->
-              error (f_"RHEV does not support the output format '%s', only raw or qcow2") t.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;
-
           { t with target_file = target_file }
-      ) targets in
+      ) (List.combine targets vol_uuids) in
+
+    (* Generate the .meta file associated with each volume. *)
+    Lib_ovf.create_meta_files verbose output_alloc esd_uuid image_uuid targets;
 
     (* Return the list of targets. *)
     targets
 
   (* This is called after conversion to write the OVF metadata. *)
   method create_metadata source targets guestcaps inspect =
-    (* This modifies the OVF DOM, adding a section for each disk. *)
-    let rec add_disks 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
-
-      (* Iterate over the disks, adding them to the OVF document. *)
-      iteri (
-        fun i ({ target_overlay = ov } as t) ->
-          let is_boot_drive = i == 0 in
-
-          let vol_uuid =
-            try Hashtbl.find vol_uuid ov.ov_sd
-            with Not_found -> assert false in
-
-          let fileref = image_uuid // vol_uuid in
-
-          let size_gb =
-            Int64.to_float ov.ov_virtual_size /. 1024. /. 1024. /. 1024. in
-          let usage_gb =
-            (* In the --no-copy case it can happen that the target file
-             * does not exist.  In that case we simply omit the
-             * ovf:actual_size attribute.
-             *)
-            if Sys.file_exists t.target_file then (
-              let usage_mb = du_m t.target_file in
-              if usage_mb > 0L then (
-                let usage_mb = Int64.to_float usage_mb /. 1024. in
-                Some usage_mb
-              ) else None
-            ) else None in
-
-          let format_for_rhev =
-            match t.target_format with
-            | "raw" -> "RAW"
-            | "qcow2" -> "COW"
-            | _ ->
-              error (f_"RHEV does not support the output format '%s', only raw or qcow2") t.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 =
-            let attrs = [
-              "ovf:diskId", vol_uuid;
-              "ovf:size", sprintf "%.1f" size_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",
-              (match guestcaps.gcaps_block_bus with
-              | Virtio_blk -> "VirtIO" | IDE -> "IDE");
-              "ovf:disk-type", "System"; (* RHBZ#744538 *)
-              "ovf:boot", if is_boot_drive then "True" else "False";
-            ] in
-            let attrs =
-              match usage_gb with
-              | None -> attrs
-              | Some usage_gb ->
-                ("ovf:actual_size", sprintf "%.1f" usage_gb) :: attrs in
-            e "Disk" attrs [] 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;
-      ) targets
-
-    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
-    in
-
-    (* This modifies the OVF DOM, adding a section for each NIC. *)
-    let add_networks ovf =
-      let nics = source.s_nics in
-      let network_section =
-        let sections = path_to_nodes ovf ["ovf:Envelope"; "Section"] in
-        try find_node_by_attr sections ("xsi:type", "ovf:NetworkSection_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
-
-      (* Iterate over the NICs, adding them to the OVF document. *)
-      iteri (
-        fun i { s_mac = mac; s_vnet_type = vnet_type;
-                s_vnet = vnet; s_vnet_orig = vnet_orig } ->
-          let dev = sprintf "eth%d" i in
-
-          let model =
-            match guestcaps.gcaps_net_bus with
-            | RTL8139 -> "1"
-            | E1000 -> "2"
-            | Virtio_net -> "3"
-            (*| bus ->
-              warning ~prog (f_"unknown NIC model %s for ethernet device %s.  This NIC will be imported as rtl8139 instead.")
-                bus dev;
-              "1" *) in
-
-          if vnet_orig <> vnet then (
-            let c = Comment (sprintf "mapped from \"%s\" to \"%s\""
-                               vnet_orig vnet) in
-            append_child c network_section
-          );
-
-          let network = e "Network" ["ovf:name", vnet] [] in
-          append_child network network_section;
-
-          let item =
-            let children = [
-              e "rasd:InstanceId" [] [PCData "3"];
-              e "rasd:Caption" [] [PCData (sprintf "Ethernet adapter on %s" vnet)];
-              e "rasd:ResourceType" [] [PCData "10"];
-              e "rasd:ResourceSubType" [] [PCData model];
-              e "rasd:Connection" [] [PCData vnet];
-              e "rasd:Name" [] [PCData dev];
-            ] in
-            let children =
-              match mac with
-              | None -> children
-              | Some mac -> children @ [e "rasd:MACAddress" [] [PCData mac]] in
-            e "Item" [] children in
-          append_child item virtualhardware_section;
-      ) nics
-    in
-
-    let memsize_mb = source.s_memory /^ 1024L /^ 1024L in
-
-    let vmtype =
-      match rhev_params.vmtype with
-      | Some vmtype -> vmtype
-      | None -> Lib_ovf.ovf_vmtype inspect in
-    let vmtype = match vmtype with `Desktop -> "DESKTOP" | `Server -> "SERVER" in
-    let ostype = Lib_ovf.ovf_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 ovf;
-
-    (* Old virt-v2v ignored removable media. XXX *)
-
-    (* Add networks to the OVF XML. *)
-    add_networks ovf;
-
-    (* Old virt-v2v didn't really look at the video and display
-     * metadata, instead just adding a single standard display (see
-     * above).  However it did warn if there was a password on the
-     * display of the old guest.
-     *)
-    (match source with
-    | { s_display = Some { s_password = Some _ } } ->
-      warning ~prog (f_"This guest required a password for connection to its display, but this is not supported by RHEV.  Therefore the converted guest's display will not require a separate password to connect.");
-    | _ -> ());
+    (* Create the metadata. *)
+    let ovf = Lib_ovf.create_ovf verbose source targets guestcaps inspect
+      output_alloc vmtype esd_uuid image_uuid vol_uuids vm_uuid in
 
     (* Write it to the metadata file. *)
-    let dir = esd.mp // esd.uuid // "master" // "vms" // vm_uuid in
+    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
diff --git a/v2v/output_rhev.mli b/v2v/output_rhev.mli
index a916ca9..4c99ea5 100644
--- a/v2v/output_rhev.mli
+++ b/v2v/output_rhev.mli
@@ -18,15 +18,10 @@
 
 (** [-o rhev] target. *)
 
-type rhev_params = {
-  image_uuid : string option;           (* --rhev-image-uuid *)
-  vol_uuids : string list;              (* --rhev-vol-uuid (multiple) *)
-  vm_uuid : string option;              (* --rhev-vm-uuid *)
-  vmtype : [`Server|`Desktop] option;   (* --vmtype *)
-}
-(** Miscellaneous extra command line parameters used by RHEV. *)
+val mount_and_check_storage_domain : bool -> string -> string -> (string * string)
+(** This helper function is also used by the VDSM target. *)
 
-val output_rhev : bool -> string -> rhev_params -> [`Sparse|`Preallocated] -> Types.output
-(** [output_rhev verbose os rhev_params output_alloc] creates and
+val output_rhev : bool -> string -> [`Server|`Desktop] option -> [`Sparse|`Preallocated] -> Types.output
+(** [output_rhev verbose os vmtype output_alloc] creates and
     returns a new {!Types.output} object specialized for writing
-    output to RHEV-M or oVirt. *)
+    output to RHEV-M or oVirt Export Storage Domain. *)
diff --git a/v2v/output_vdsm.ml b/v2v/output_vdsm.ml
new file mode 100644
index 0000000..08d3f09
--- /dev/null
+++ b/v2v/output_vdsm.ml
@@ -0,0 +1,156 @@
+(* 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
+
+type vdsm_params = {
+  image_uuid : string;
+  vol_uuids : string list;
+  vm_uuid : string;
+}
+
+class output_vdsm verbose os vdsm_params vmtype output_alloc =
+object
+  inherit output verbose
+
+  method as_options =
+    sprintf "-o vdsm -os %s --vdsm-image-uuid %s%s --vdsm-vm-uuid %s%s" os
+      vdsm_params.image_uuid
+      (String.concat ""
+         (List.map (sprintf " --vdsm-vol-uuid %s") vdsm_params.vol_uuids))
+      vdsm_params.vm_uuid
+      (match vmtype with
+      | None -> ""
+      | Some `Server -> " --vmtype server"
+      | Some `Desktop -> " --vmtype desktop")
+
+  (* RHEV doesn't support serial consoles.  This causes the conversion
+   * step to remove it.
+   *)
+  method keep_serial_console = false
+
+  (* Data Domain mountpoint. *)
+  val mutable dd_mp = ""
+  val mutable dd_uuid = ""
+
+  (* Target image directory. *)
+  val mutable image_dir = ""
+
+  (* Target metadata directory. *)
+  val mutable ovf_dir = ""
+
+  (* 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.  'targets' 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.
+   *)
+  method prepare_targets _ targets =
+    if List.length vdsm_params.vol_uuids <> List.length targets then
+      error (f_"the number of '--vdsm-vol-uuid' parameters passed on the command line has to match the number of guest disk images (for this guest: %d)")
+        (List.length targets);
+
+    let mp, uuid =
+      Output_rhev.mount_and_check_storage_domain verbose (s_"Data Domain") os in
+    dd_mp <- mp;
+    dd_uuid <- uuid;
+    if verbose then
+      eprintf "VDSM: DD mountpoint: %s\nVDSM: DD UUID: %s\n%!"
+        dd_mp dd_uuid;
+
+    (* Note that VDSM has to create this directory. *)
+    image_dir <- dd_mp // dd_uuid // "images" // vdsm_params.image_uuid;
+    if not (is_directory image_dir) then
+      error (f_"image directory (%s) does not exist or is not a directory")
+        image_dir;
+
+    if verbose then
+      eprintf "VDSM: image directory: %s\n%!" image_dir;
+
+    (* Note that VDSM has to create this directory too. *)
+    ovf_dir <- dd_mp // dd_uuid // "master" // "vms" // vdsm_params.vm_uuid;
+    if not (is_directory ovf_dir) then
+      error (f_"OVF (metadata) directory (%s) does not exist or is not a directory")
+        ovf_dir;
+
+    if verbose then
+      eprintf "VDSM: OVF (metadata) directory: %s\n%!" ovf_dir;
+
+    (* The final directory structure should look 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   #
+     *)
+
+    (* Create the target filenames. *)
+    let targets =
+      List.map (
+        fun ({ target_overlay = ov } as t, vol_uuid) ->
+          let ov_sd = ov.ov_sd in
+          let target_file = image_dir // vol_uuid in
+
+          if verbose then
+            eprintf "VDSM: will export %s to %s\n%!" ov_sd target_file;
+
+          { t with target_file = target_file }
+      ) (List.combine targets vdsm_params.vol_uuids) in
+
+    (* Generate the .meta files associated with each volume. *)
+    Lib_ovf.create_meta_files verbose output_alloc dd_uuid
+      vdsm_params.image_uuid targets;
+
+    (* Return the list of targets. *)
+    targets
+
+  (* This is called after conversion to write the OVF metadata. *)
+  method create_metadata source targets guestcaps inspect =
+    (* Create the metadata. *)
+    let ovf = Lib_ovf.create_ovf verbose source targets guestcaps inspect
+      output_alloc vmtype dd_uuid
+      vdsm_params.image_uuid
+      vdsm_params.vol_uuids
+      vdsm_params.vm_uuid in
+
+    (* Write it to the metadata file. *)
+    let file = ovf_dir // vdsm_params.vm_uuid ^ ".ovf" in
+    let chan = open_out file in
+    doc_to_chan chan ovf;
+    close_out chan
+end
+
+let output_vdsm = new output_vdsm
+let () = Modules_list.register_output_module "vdsm"
diff --git a/v2v/output_rhev.mli b/v2v/output_vdsm.mli
similarity index 60%
copy from v2v/output_rhev.mli
copy to v2v/output_vdsm.mli
index a916ca9..56ddf55 100644
--- a/v2v/output_rhev.mli
+++ b/v2v/output_vdsm.mli
@@ -16,17 +16,16 @@
  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
  *)
 
-(** [-o rhev] target. *)
+(** [-o vdsm] target. *)
 
-type rhev_params = {
-  image_uuid : string option;           (* --rhev-image-uuid *)
-  vol_uuids : string list;              (* --rhev-vol-uuid (multiple) *)
-  vm_uuid : string option;              (* --rhev-vm-uuid *)
-  vmtype : [`Server|`Desktop] option;   (* --vmtype *)
+type vdsm_params = {
+  image_uuid : string;                (* --vdsm-image-uuid *)
+  vol_uuids : string list;            (* --vdsm-vol-uuid (multiple) *)
+  vm_uuid : string;                   (* --vdsm-vm-uuid *)
 }
-(** Miscellaneous extra command line parameters used by RHEV. *)
+(** Miscellaneous extra command line parameters used by VDSM. *)
 
-val output_rhev : bool -> string -> rhev_params -> [`Sparse|`Preallocated] -> Types.output
-(** [output_rhev verbose os rhev_params output_alloc] creates and
+val output_vdsm : bool -> string -> vdsm_params -> [`Server|`Desktop] option -> [`Sparse|`Preallocated] -> Types.output
+(** [output_vdsm verbose os rhev_params output_alloc] creates and
     returns a new {!Types.output} object specialized for writing
-    output to RHEV-M or oVirt. *)
+    output to Data Domains directly under VDSM control. *)
diff --git a/v2v/test-v2v-o-rhev-options.sh b/v2v/test-v2v-o-vdsm-options.sh
similarity index 85%
rename from v2v/test-v2v-o-rhev-options.sh
rename to v2v/test-v2v-o-vdsm-options.sh
index 401cec9..e5bf764 100755
--- a/v2v/test-v2v-o-rhev-options.sh
+++ b/v2v/test-v2v-o-vdsm-options.sh
@@ -16,13 +16,13 @@
 # along with this program; if not, write to the Free Software
 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 
-# Test -o rhev options: --vmtype and --rhev-*-uuid
+# Test -o vdsm options: --vmtype and --vdsm-*-uuid
 
 unset CDPATH
 export LANG=C
 set -e
 
-if [ -n "$SKIP_TEST_V2V_O_RHEV_OPTIONS_SH" ]; then
+if [ -n "$SKIP_TEST_V2V_O_VDSM_OPTIONS_SH" ]; then
     echo "$0: test skipped because environment variable is set"
     exit 77
 fi
@@ -47,26 +47,28 @@ if ! test -r $virt_tools_data_dir/rhsrvany.exe; then
     exit 77
 fi
 
-d=test-v2v-o-rhev-options.d
+d=test-v2v-o-vdsm-options.d
 rm -rf $d
 mkdir $d
 
 # Create a dummy Export Storage Domain.
 mkdir $d/12345678-1234-1234-1234-123456789abc
 mkdir $d/12345678-1234-1234-1234-123456789abc/images
+mkdir $d/12345678-1234-1234-1234-123456789abc/images/IMAGE
 mkdir $d/12345678-1234-1234-1234-123456789abc/master
 mkdir $d/12345678-1234-1234-1234-123456789abc/master/vms
+mkdir $d/12345678-1234-1234-1234-123456789abc/master/vms/VM
 
-# The --rhev-*-uuid options don't actually check that the
+# The --vdsm-*-uuid options don't actually check that the
 # parameter is a UUID, which is useful here.
 
 $VG ./virt-v2v --debug-gc \
     -i libvirt -ic "$libvirt_uri" windows \
-    -o rhev -os $d \
+    -o vdsm -os $d \
     --vmtype desktop \
-    --rhev-image-uuid IMAGE \
-    --rhev-vol-uuid VOL \
-    --rhev-vm-uuid VM \
+    --vdsm-image-uuid IMAGE \
+    --vdsm-vol-uuid VOL \
+    --vdsm-vm-uuid VM \
 
 # Test the OVF metadata was created.
 test -f $d/12345678-1234-1234-1234-123456789abc/master/vms/VM/VM.ovf
diff --git a/v2v/virt-v2v.pod b/v2v/virt-v2v.pod
index 08040d0..5aa1c91 100644
--- a/v2v/virt-v2v.pod
+++ b/v2v/virt-v2v.pod
@@ -33,13 +33,13 @@ libguestfs E<ge> 1.28.
                          ┌────────────┐
  -i disk ───────────┐    │            │   ┌───────▶ -o local
  -i ova  ─────────┐ └──▶ │ virt-v2v   │   │
-                  └────▶ │ conversion │ ──┘
- ESX ──▶┌────────────┐   │ server     │    ┌────────────┐
- Xen ──▶│ -i libvirt ──▶ │            │ ───▶ -o libvirt │─▶ KVM
- ... ──▶│  (default) │   │            │    │  (default) │
-        └────────────┘   │            │ ──┐└────────────┘
- -i libvirtxml ────────▶ │            │ ─┐└──────▶ -o glance
-                         └────────────┘  └─────────▶ -o rhev
+                  └────▶ │ conversion │ ──┘┌────────────┐
+ ESX ──▶┌────────────┐   │ server     │ ───▶ -o libvirt │─▶ KVM
+ Xen ──▶│ -i libvirt ──▶ │            │    │  (default) │
+ ... ──▶│  (default) │   │            │ ──┐└────────────┘
+        └────────────┘   │            │ ─┐└──────▶ -o glance
+ -i libvirtxml ────────▶ │            │ ┐└─────────▶ -o rhev
+                         └────────────┘ └──────────▶ -o vdsm
 
 Virt-v2v has a number of possible input and output modes, selected
 using the I<-i> and I<-o> options.  Only one input and output mode can
@@ -66,7 +66,8 @@ the precise libvirt target.
 
 I<-o local> is used to write to a local disk image (mainly for testing).
 
-I<-o rhev> is used to write to a RHEV-M / oVirt target.
+I<-o rhev> is used to write to a RHEV-M / oVirt target.  I<-o vdsm>
+is only used when virt-v2v runs under VDSM control.
 
 =head1 EXAMPLES
 
@@ -291,6 +292,13 @@ I<-os> parameter must also be used to specify the location of the
 Export Storage Domain.  Note this does not actually import the guest
 into RHEV.  You have to do that manually later using the UI.
 
+=item B<-o vdsm>
+
+Set the output method to I<vdsm>.
+
+This mode is similar to I<-o rhev> but is only used by RHEV VDSM
+when it runs virt-v2v under VDSM control.
+
 =item B<-oa sparse>
 
 =item B<-oa preallocated>
@@ -354,44 +362,6 @@ See L</NETWORKS AND BRIDGES>.
 
 This disables progress bars and other unnecessary output.
 
-=item B<--rhev-image-uuid> UUID
-
-=item B<--rhev-vol-uuid> UUID
-
-=item B<--rhev-vm-uuid> UUID
-
-Normally the RHEV output mode chooses a random UUID for each of:
-
-=over 4
-
-=item *
-
-the image directory (I<--rhev-image-uuid>)
-
-=item *
-
-once for each guest disk (I<--rhev-vol-uuid>)
-
-=item *
-
-the VM and OVF file (I<--rhev-vm-uuid>).
-
-=back
-
-However you can force specific UUIDs instead by using these options.
-
-You can use I<--rhev-image-uuid> and I<--rhev-vm-uuid> on the command
-line at most once.
-
-If the guest has more than one virtual disk, then you should use
-I<--rhev-vol-uuid> multiple times to change the UUID of each guest
-disk.
-
-The format of UUIDs is: C<12345678-1234-1234-1234-123456789abc> (each
-hex digit can be C<0-9> or C<a-f>), conforming to S<OSF DCE 1.1>.
-
-These options can only be used with I<-o rhev>.
-
 =item B<--root ask>
 
 =item B<--root single>
@@ -435,6 +405,39 @@ boot an operating system from the first VirtIO disk.  Specifically,
 C</boot> must be on the first VirtIO disk, and it cannot chainload an
 OS which is not in the first VirtIO disk.
 
+=item B<--vdsm-image-uuid> UUID
+
+=item B<--vdsm-vol-uuid> UUID
+
+=item B<--vdsm-vm-uuid> UUID
+
+Normally the RHEV output mode chooses random UUIDs for the target
+guest.  However VDSM needs to control the UUIDs and passes these
+parameters when virt-v2v runs under VDSM control.  The parameters
+control:
+
+=over 4
+
+=item *
+
+the image directory (I<--vdsm-image-uuid>)
+
+=item *
+
+UUIDs for each guest disk (I<--vdsm-vol-uuid>) (this option
+is passed once for each guest disk)
+
+=item *
+
+the VM and OVF file (I<--vdsm-vm-uuid>).
+
+=back
+
+The format of UUIDs is: C<12345678-1234-1234-1234-123456789abc> (each
+hex digit can be C<0-9> or C<a-f>), conforming to S<OSF DCE 1.1>.
+
+These options can only be used with I<-o vdsm>.
+
 =item B<-v>
 
 =item B<--verbose>
@@ -451,10 +454,10 @@ Display version number and exit.
 
 =item B<--vmtype server>
 
-For the RHEV target only, specify the type of guest.  You can set this
-to C<desktop> or C<server>.  If the option is not given, then a
-suitable default is chosen based on the detected guest operating
-system.
+For the I<-o rhev> or I<-o vdsm> targets only, specify the type of
+guest.  You can set this to C<desktop> or C<server>.  If the option is
+not given, then a suitable default is chosen based on the detected
+guest operating system.
 
 =item B<-x>
 

-- 
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