[Libguestfs] [PATCH] v2v: add --in-place mode

Roman Kagan rkagan at virtuozzo.com
Mon Jul 27 16:29:57 UTC 2015


In this mode, converting of the VM configuration, setting up the
rollback path for error cases, transforming the VM storage and so on is
taken care of by a third-party toolset, and virt-v2v is only supposed to
tune up the guest OS directly inside the source VM, to enable it to boot
and run under the input hypervisor.

Signed-off-by: Roman Kagan <rkagan at virtuozzo.com>
---
 tests/guests/guests.xml.in |  16 +++++++
 v2v/Makefile.am            |   1 +
 v2v/cmdline.ml             |   7 +++-
 v2v/test-v2v-in-place.sh   |  81 +++++++++++++++++++++++++++++++++++
 v2v/v2v.ml                 | 102 +++++++++++++++++++++++++++------------------
 v2v/virt-v2v.pod           |  17 ++++++++
 6 files changed, 183 insertions(+), 41 deletions(-)
 create mode 100755 v2v/test-v2v-in-place.sh

diff --git a/tests/guests/guests.xml.in b/tests/guests/guests.xml.in
index 8f7ac81..6f08b80 100644
--- a/tests/guests/guests.xml.in
+++ b/tests/guests/guests.xml.in
@@ -279,4 +279,20 @@
     </devices>
   </domain>
 
+  <domain type='test'>
+    <name>windows-overlay</name>
+    <memory>1048576</memory>
+    <os>
+      <type>hvm</type>
+      <boot dev='hd'/>
+    </os>
+    <devices>
+      <disk type='file' device='disk'>
+        <driver name='qemu' type='qcow2'/>
+        <source file='@abs_builddir@/windows-overlay.qcow2'/>
+        <target dev='vda' bus='virtio'/>
+      </disk>
+    </devices>
+  </domain>
+
 </node>
diff --git a/v2v/Makefile.am b/v2v/Makefile.am
index 06da002..dae063c 100644
--- a/v2v/Makefile.am
+++ b/v2v/Makefile.am
@@ -232,6 +232,7 @@ TESTS += \
 	test-v2v-cdrom.sh \
 	test-v2v-i-ova.sh \
 	test-v2v-i-disk.sh \
+	test-v2v-in-place.sh \
 	test-v2v-machine-readable.sh \
 	test-v2v-networks-and-bridges.sh \
 	test-v2v-no-copy.sh \
diff --git a/v2v/cmdline.ml b/v2v/cmdline.ml
index eaf57dc..2a3224a 100644
--- a/v2v/cmdline.ml
+++ b/v2v/cmdline.ml
@@ -37,6 +37,7 @@ let parse_cmdline () =
   let output_format = ref "" in
   let output_name = ref "" in
   let output_storage = ref "" in
+  let in_place = ref false in
   let password_file = ref "" in
   let print_source = ref false in
   let qemu_boot = ref false in
@@ -160,6 +161,7 @@ let parse_cmdline () =
     "-of",       Arg.Set_string output_format, "raw|qcow2 " ^ s_"Set output format";
     "-on",       Arg.Set_string output_name, "name " ^ s_"Rename guest when converting";
     "-os",       Arg.Set_string output_storage, "storage " ^ s_"Set output storage location";
+    "--in-place", Arg.Set in_place,         " " ^ s_"Only tune the guest in the input VM";
     "--password-file", Arg.Set_string password_file, "file " ^ s_"Use password from file";
     "--print-source", Arg.Set print_source, " " ^ s_"Print source and stop";
     "--qemu-boot", Arg.Set qemu_boot,       " " ^ s_"Boot in qemu (-o qemu only)";
@@ -226,6 +228,7 @@ read the man page virt-v2v(1).
   let output_mode = !output_mode in
   let output_name = match !output_name with "" -> None | s -> Some s in
   let output_storage = !output_storage in
+  let in_place = !in_place in
   let password_file = match !password_file with "" -> None | s -> Some s in
   let print_source = !print_source in
   let qemu_boot = !qemu_boot in
@@ -305,6 +308,8 @@ read the man page virt-v2v(1).
       Input_ova.input_ova filename in
 
   (* Parse the output mode. *)
+  if output_mode <> `Not_set && in_place then
+      error (f_"-o and --in-place cannot be used at the same time");
   let output =
     match output_mode with
     | `Glance ->
@@ -386,5 +391,5 @@ read the man page virt-v2v(1).
 
   input, output,
   debug_gc, debug_overlays, do_copy, network_map, no_trim,
-  output_alloc, output_format, output_name,
+  output_alloc, output_format, output_name, in_place,
   print_source, root_choice
diff --git a/v2v/test-v2v-in-place.sh b/v2v/test-v2v-in-place.sh
new file mode 100755
index 0000000..c19707e
--- /dev/null
+++ b/v2v/test-v2v-in-place.sh
@@ -0,0 +1,81 @@
+#!/bin/bash -
+# libguestfs virt-v2v test script
+# Copyright (C) 2014 Red Hat Inc.
+# Copyright (C) 2015 Parallels IP Holdings GmbH.
+#
+# 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.
+
+# Test --in-place.
+
+unset CDPATH
+export LANG=C
+set -e
+
+if [ -n "$SKIP_TEST_V2V_IN_PLACE_SH" ]; then
+    echo "$0: test skipped because environment variable is set"
+    exit 77
+fi
+
+if [ "$(guestfish get-backend)" = "uml" ]; then
+    echo "$0: test skipped because UML backend does not support network"
+    exit 77
+fi
+
+# You shouldn't be running the tests as root anyway, but in this case
+# it's especially bad because we don't want to start creating guests
+# or storage pools in the system namespace.
+if [ "$(id -u)" -eq 0 ]; then
+    echo "$0: test skipped because you're running tests as root.  Don't do that!"
+    exit 77
+fi
+
+guests_dir="$(cd $(dirname $0)/../tests/guests && pwd)"
+libvirt_uri="test://$guests_dir/guests.xml"
+
+f="$guests_dir/windows.img"
+if ! test -f $f || ! test -s $f; then
+    echo "$0: test skipped because phony Windows image was not created"
+    exit 77
+fi
+
+virt_tools_data_dir=${VIRT_TOOLS_DATA_DIR:-/usr/share/virt-tools}
+if ! test -r $virt_tools_data_dir/rhsrvany.exe; then
+    echo "$0: test skipped because rhsrvany.exe is not installed"
+    exit 77
+fi
+
+fo="$guests_dir/windows-overlay.qcow2"
+rm -f $fo
+qemu-img create -f qcow2 -b $f -o compat=1.1,backing_fmt=raw $fo
+md5="$(md5sum $f)"
+
+$VG virt-v2v --debug-gc \
+    -i libvirt -ic "$libvirt_uri" windows-overlay \
+    --in-place
+
+# Test some aspects of the target disk image.
+guestfish --ro -a $fo -i <<EOF
+  is-dir "/Program Files/Red Hat/Firstboot"
+  is-file "/Program Files/Red Hat/Firstboot/firstboot.bat"
+  is-dir "/Program Files/Red Hat/Firstboot/scripts"
+  is-dir "/Windows/Drivers/VirtIO"
+EOF
+
+
+# Test the base image remained untouched
+test "$md5" = "$(md5sum $f)"
+
+# Clean up.
+rm -r $fo
diff --git a/v2v/v2v.ml b/v2v/v2v.ml
index 242f129..b744056 100644
--- a/v2v/v2v.ml
+++ b/v2v/v2v.ml
@@ -47,7 +47,8 @@ let rec main () =
   (* Handle the command line. *)
   let input, output,
     debug_gc, debug_overlays, do_copy, network_map, no_trim,
-    output_alloc, output_format, output_name, print_source, root_choice =
+    output_alloc, output_format, output_name, in_place, print_source,
+    root_choice =
     Cmdline.parse_cmdline () in
 
   (* Print the version, easier than asking users to tell us. *)
@@ -117,52 +118,70 @@ let rec main () =
     ) nics in
     { source with s_nics = nics } in
 
-  (* Create a qcow2 v3 overlay to protect the source image(s).  There
-   * is a specific reason to use the newer qcow2 variant: Because the
-   * L2 table can store zero clusters efficiently, and because
-   * discarded blocks are stored as zero clusters, this should allow us
-   * to fstrim/blkdiscard and avoid copying significant parts of the
-   * data over the wire.
-   *)
-  message (f_"Creating an overlay to protect the source from being modified");
   let overlay_dir = (new Guestfs.guestfs ())#get_cachedir () in
-  let overlays =
-    List.map (
-      fun ({ s_qemu_uri = qemu_uri; s_format = format } as source) ->
-        let overlay_file =
-          Filename.temp_file ~temp_dir:overlay_dir "v2vovl" ".qcow2" in
-        unlink_on_exit overlay_file;
-
-        let options =
-          "compat=1.1" ^
-            (match format with None -> ""
-            | Some fmt -> ",backing_fmt=" ^ fmt) in
-        let cmd =
-          sprintf "qemu-img create -q -f qcow2 -b %s -o %s %s"
-            (quote qemu_uri) (quote options) overlay_file in
-        if verbose () then printf "%s\n%!" cmd;
-        if Sys.command cmd <> 0 then
-          error (f_"qemu-img command failed, see earlier errors");
-
-        (* Sanity check created overlay (see below). *)
-        if not ((new G.guestfs ())#disk_has_backing_file overlay_file) then
-          error (f_"internal error: qemu-img did not create overlay with backing file");
-
-        overlay_file, source
-    ) source.s_disks in
+  let overlays = (
+    if not in_place then (
+      (* Create a qcow2 v3 overlay to protect the source image(s).  There
+       * is a specific reason to use the newer qcow2 variant: Because the
+       * L2 table can store zero clusters efficiently, and because
+       * discarded blocks are stored as zero clusters, this should allow us
+       * to fstrim/blkdiscard and avoid copying significant parts of the
+       * data over the wire.
+       *)
+      message (f_"Creating an overlay to protect the source from being modified");
+      List.map (
+        fun ({ s_qemu_uri = qemu_uri; s_format = format } as source) ->
+          let overlay_file =
+            Filename.temp_file ~temp_dir:overlay_dir "v2vovl" ".qcow2" in
+          unlink_on_exit overlay_file;
+
+          let options =
+            "compat=1.1" ^
+              (match format with None -> ""
+              | Some fmt -> ",backing_fmt=" ^ fmt) in
+          let cmd =
+            sprintf "qemu-img create -q -f qcow2 -b %s -o %s %s"
+              (quote qemu_uri) (quote options) overlay_file in
+          if verbose () then printf "%s\n%!" cmd;
+          if Sys.command cmd <> 0 then
+            error (f_"qemu-img command failed, see earlier errors");
+
+          (* Sanity check created overlay (see below). *)
+          if not ((new G.guestfs ())#disk_has_backing_file overlay_file) then
+            error (f_"internal error: qemu-img did not create overlay with backing file");
+
+          overlay_file, source
+      ) source.s_disks
+    ) else []
+  ) in
 
   (* Open the guestfs handle. *)
-  message (f_"Opening the overlay");
+  if in_place then
+    message (f_"Opening the guest disks")
+  else
+    message (f_"Opening the overlay");
   let g = new G.guestfs () in
   if trace () then g#set_trace true;
   if verbose () then g#set_verbose true;
   g#set_network true;
-  List.iter (
-    fun (overlay_file, _) ->
-      g#add_drive_opts overlay_file
-        ~format:"qcow2" ~cachemode:"unsafe" ~discard:"besteffort"
-        ~copyonread:true
-  ) overlays;
+  if in_place then (
+    List.iter (
+      fun ({s_qemu_uri = qemu_uri; s_format = format}) ->
+        match format with
+        | None ->
+            g#add_drive_opts qemu_uri ~cachemode:"unsafe" ~discard:"besteffort"
+        | Some fmt ->
+            g#add_drive_opts qemu_uri ~format:fmt ~cachemode:"unsafe"
+              ~discard:"besteffort"
+    ) source.s_disks
+  ) else (
+    List.iter (
+      fun (overlay_file, _) ->
+        g#add_drive_opts overlay_file
+          ~format:"qcow2" ~cachemode:"unsafe" ~discard:"besteffort"
+          ~copyonread:true
+    ) overlays
+  );
 
   g#launch ();
 
@@ -294,6 +313,9 @@ let rec main () =
   g#shutdown ();
   g#close ();
 
+  if in_place then
+    exit 0;
+
   (* Does the guest require UEFI on the target? *)
   message (f_"Checking if the guest needs BIOS or UEFI to boot");
   let target_firmware =
diff --git a/v2v/virt-v2v.pod b/v2v/virt-v2v.pod
index ef2dee1..eda1cf7 100644
--- a/v2v/virt-v2v.pod
+++ b/v2v/virt-v2v.pod
@@ -9,6 +9,8 @@ virt-v2v - Convert a guest to use KVM
  virt-v2v -ic vpx://vcenter.example.com/Datacenter/esxi vmware_guest \
    -o rhev -os rhev.nfs:/export_domain --network rhevm
 
+ virt-v2v -ic qemu:///system qemu_guest --in-place
+
  virt-v2v -i libvirtxml guest-domain.xml -o local -os /var/tmp
 
  virt-v2v -i disk disk.img -o local -os /var/tmp
@@ -75,6 +77,9 @@ booting the guest directly in qemu (mainly for testing).
 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.
 
+I<--in-place> instructs virt-v2v to customize the guest OS in the input
+virtual machine, instead of creating a new VM in the target hypervisor.
+
 =head1 EXAMPLES
 
 =head2 Convert from VMware vCenter server to local libvirt
@@ -518,6 +523,18 @@ C<root>.
 You will get an error if virt-v2v is unable to mount/write to the
 Export Storage Domain.
 
+=item B<--in-place>
+
+Do not create an output virtual machine in the target hypervisor.
+Instead, adjust the guest OS in the source VM to run in the input
+hypervisor.
+
+This mode is meant for integration with other toolsets, which take the
+responsibility of converting the VM configuration, providing for
+rollback in case of errors, transforming the storage, etc.
+
+Conflicts with all I<-o *> options.
+
 =item B<--password-file> file
 
 Instead of asking for password(s) interactively, pass the password
-- 
2.4.3




More information about the Libguestfs mailing list