[Pkg-libvirt-commits] [libguestfs] 200/266: v2v: esx: Add / fix ability to import guests from vCenter Server.

Hilko Bengen bengen at moszumanska.debian.org
Fri Oct 3 14:42:01 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 155a6f0482e5eeb6a763d4d31354edc5f48a1f43
Author: Richard W.M. Jones <rjones at redhat.com>
Date:   Tue Aug 26 19:34:37 2014 +0100

    v2v: esx: Add / fix ability to import guests from vCenter Server.
    
    See additional information in the manual page for details.
---
 README               |   3 +-
 v2v/input_libvirt.ml |  12 +++-
 v2v/lib_esx.ml       | 186 ++++++++++++++++++++++++++++++++++++++++++++-------
 v2v/lib_esx.mli      |   2 +-
 v2v/utils.ml         |  13 ++++
 v2v/virt-v2v.pod     |  90 +++++++++++++++++++++++--
 6 files changed, 272 insertions(+), 34 deletions(-)

diff --git a/README b/README
index aa03ee8..b88a67d 100644
--- a/README
+++ b/README
@@ -52,7 +52,8 @@ The full requirements are described below.
 +--------------+-------------+---+-----------------------------------------+
 | qemu         | 1.2.0       | R | 1.1 may work, but has broken virtio-scsi|
 +--------------+-------------+---+-----------------------------------------+
-| qemu-img     |             | R |                                         |
+| qemu-img     |             | R | >= 2.2.0 is required for virt-v2v but   |
+|              |             |   | optional elsewhere                      |
 +--------------+-------------+---+-----------------------------------------+
 | kernel       | 2.6.34      | R | Make sure the following are enabled     |
 |              |             |   | compiled in or as a module:             |
diff --git a/v2v/input_libvirt.ml b/v2v/input_libvirt.ml
index 6449dc3..f75df00 100644
--- a/v2v/input_libvirt.ml
+++ b/v2v/input_libvirt.ml
@@ -61,8 +61,16 @@ object
         | Some _, Some "" ->
           None, None
 
-        | Some _, Some "esx" ->         (* esx://... *)
-          let f = Lib_esx.map_path_to_uri uri in
+        | Some server, Some ("esx"|"gsx"|"vpx" as scheme) -> (* ESX *)
+          (* Check the backend is not libvirt.  Works around a libvirt bug
+           * (RHBZ#1134592).
+           *)
+          let libguestfs_backend = (new Guestfs.guestfs ())#get_backend () in
+          if libguestfs_backend = "libvirt" then (
+            error (f_"ESX: because of libvirt bug https://bugzilla.redhat.com/show_bug.cgi?id=1134592 you must set this environment variable:\n\nexport LIBGUESTFS_BACKEND=direct\n\nand then rerun the virt-v2v command.")
+          );
+
+          let f = Lib_esx.map_path_to_uri verbose uri scheme server in
           Some f, Some f
 
         (* XXX Missing: Look for qemu+ssh://, xen+ssh:// and use an ssh
diff --git a/v2v/lib_esx.ml b/v2v/lib_esx.ml
index feef090..7966358 100644
--- a/v2v/lib_esx.ml
+++ b/v2v/lib_esx.ml
@@ -18,6 +18,9 @@
 
 (** Functions for dealing with ESX. *)
 
+open Common_gettext.Gettext
+open Common_utils
+
 open Xml
 open Utils
 
@@ -25,6 +28,8 @@ open Printf
 
 let esx_re = Str.regexp "^\\[\\(.*\\)\\] \\(.*\\)\\.vmdk$"
 
+let session_cookie = ref ""
+
 (* Map an ESX <source/> to a qemu URI using the cURL driver
  * in qemu.  The 'path' will be something like
  *
@@ -32,48 +37,183 @@ let esx_re = Str.regexp "^\\[\\(.*\\)\\] \\(.*\\)\\.vmdk$"
  *
  * including those literal spaces in the string.
  *
- * We want to convert that into the following URL:
- *   "https://user:password@server/folder/Fedora 20/Fedora 20-flat.vmdk" ^
- *     "?dcPath=ha-datacenter&dsName=datastore1"
- *
- * Note that the URL we create here is passed to qemu-img and is
- * ultimately parsed by curl_easy_setopt (CURLOPT_URL).
- *
  * XXX Old virt-v2v could also handle snapshots, ie:
  *
  *   "[datastore1] Fedora 20/Fedora 20-NNNNNN.vmdk"
  *
- * However this requires access to the server which we don't necessarily
- * have here.
+ * XXX Need to handle templates.  The file is called "-delta.vmdk" in
+ * place of "-flat.vmdk".
  *)
-let map_path_to_uri uri path format =
+let rec map_path_to_uri verbose uri scheme server path format =
   if not (Str.string_match esx_re path 0) then
     path, format
   else (
     let datastore = Str.matched_group 1 path
-    and vmdk = Str.matched_group 2 path in
-
-    let user =
-      match uri.uri_user with
-      | None -> ""
-      | Some user -> user ^ "@" (* No need to quote it, see RFC 2617. *) in
-    let server =
-      match uri.uri_server with
-      | None -> assert false (* checked by caller *)
-      | Some server -> server in
+    and path = Str.matched_group 2 path in
+
+    (* Get the datacenter. *)
+    let datacenter = get_datacenter uri scheme in
+
     let port =
       match uri.uri_port with
       | 443 -> ""
       | n when n >= 1 -> ":" ^ string_of_int n
       | _ -> "" in
 
-    let qemu_uri =
+    let url =
       sprintf
-        "https://%s%s%s/folder/%s-flat.vmdk?dcPath=ha-datacenter&dsName=%s"
-        user server port vmdk (uri_quote datastore) in
+        "https://%s%s/folder/%s-flat.vmdk?dcPath=%s&dsName=%s"
+        server port
+        (uri_quote path) (uri_quote datacenter) (uri_quote datastore) in
+
+    (* If no_verify=1 was passed in the libvirt URI, then we have to
+     * turn off certificate verification here too.
+     *)
+    let sslverify =
+      match uri.uri_query_raw with
+      | None -> true
+      | Some query ->
+        (* XXX only works if the query string is not URI-quoted *)
+        string_find query "no_verify=1" = -1 in
+
+    (* Now we have to query the server to get the session cookie. *)
+    let session_cookie = get_session_cookie verbose uri sslverify url in
+
+    (* Construct the JSON parameters. *)
+    let json_params = [
+      "file.driver", "https";
+      "file.url", url;
+      "file.timeout", "60";
+    ] in
+
+    let json_params =
+      if sslverify then json_params
+      else ("file.sslverify", "off") :: json_params in
+
+    let json_params =
+      match session_cookie with
+      | None -> json_params
+      | Some cookie -> ("file.cookie", cookie) :: json_params in
+
+    if verbose then (
+      printf "esx: json parameters:\n";
+      List.iter (fun (n, v) -> printf "  %s : %s\n" n v) json_params
+    );
+
+    (* Turn the JSON parameters into a 'json:' protocol string.
+     *
+     * Notes:
+     *
+     * (1) This requires qemu-img >= 2.1.0.
+     *
+     * (2) We don't 'quote' the commas, because in {!V2v} we pass the
+     * option to qemu-img create -b (not -o backing_file=...) and so
+     * extra quoting of commas is not required.  However if we changed
+     * things in future then we might requiring quoting of commas to be
+     * added somewhere.
+     *)
+    let qemu_uri =
+      sprintf "json: { %s }"
+        (String.concat " , " (
+          List.map
+            (fun (n, v) -> sprintf "\"%s\" : \"%s\"" n (json_quote v))
+            json_params
+         )
+        ) in
 
     (* The libvirt ESX driver doesn't normally specify a format, but
      * the format of the -flat file is *always* raw, so force it here.
      *)
     qemu_uri, Some "raw"
   )
+
+and get_datacenter uri scheme =
+  let default_dc = "ha-datacenter" in
+  match scheme with
+  | "vpx" ->           (* Hopefully the first part of the path. *)
+    (match uri.uri_path with
+    | None ->
+      warning ~prog (f_"esx: URI (-ic parameter) contains no path, so we cannot determine the datacenter name");
+      default_dc
+    | Some path ->
+      let path =
+        let len = String.length path in
+        if len > 0 && path.[0] = '/' then
+          String.sub path 1 (len-1)
+        else path in
+      let len =
+        try String.index path '/' with Not_found -> String.length path in
+      String.sub path 0 len
+    );
+  | "esx" -> (* Connecting to an ESXi hypervisor directly, so it's fixed. *)
+    default_dc
+  | _ ->                            (* Don't know, so guess. *)
+    default_dc
+
+and get_session_cookie verbose uri sslverify url =
+  (* Memoize the session cookie. *)
+  if !session_cookie <> "" then
+    Some !session_cookie
+  else (
+    let cmd =
+      sprintf "curl -s%s%s%s -I %s ||:"
+        (if not sslverify then " --insecure" else "")
+        (match uri.uri_user with Some _ -> " -u" | None -> "")
+        (match uri.uri_user with Some user -> " " ^ quote user | None -> "")
+        (quote url) in
+    let lines = external_command ~prog cmd in
+
+    let dump_response chan =
+      fprintf chan "%s\n" cmd;
+      List.iter (fun x -> fprintf chan "%s\n" x) lines
+    in
+
+    if verbose then dump_response stdout;
+
+    (* Look for the last HTTP/x.y NNN status code in the output. *)
+    let status = ref "" in
+    List.iter (
+      fun line ->
+        let len = String.length line in
+        if len >= 12 && String.sub line 0 5 = "HTTP/" then
+          status := String.sub line 9 3
+    ) lines;
+    let status = !status in
+    if status = "" then (
+      dump_response stderr;
+      error (f_"esx: no status code in output of 'curl' command.  Is 'curl' installed?")
+    );
+
+    if status = "401" then (
+      dump_response stderr;
+      error (f_"esx: incorrect username or password")
+    );
+
+    if status = "404" then (
+      dump_response stderr;
+      error (f_"esx: URL not found: %s") url
+    );
+
+    if status <> "200" then (
+      dump_response stderr;
+      error (f_"esx: invalid response from server")
+    );
+
+    (* Get the cookie. *)
+    List.iter (
+      fun line ->
+        let len = String.length line in
+        if len >= 12 && String.sub line 0 12 = "Set-Cookie: " then (
+          let line = String.sub line 12 (len-12) in
+          let cookie, _ = string_split ";" line in
+          session_cookie := cookie
+        )
+    ) lines;
+    if !session_cookie = "" then (
+      dump_response stderr;
+      warning ~prog (f_"esx: could not read session cookie from the vCenter Server, conversion may consume all sessions on the server and fail part way through");
+      None
+    )
+    else
+      Some !session_cookie
+  )
diff --git a/v2v/lib_esx.mli b/v2v/lib_esx.mli
index 626ebd9..e504098 100644
--- a/v2v/lib_esx.mli
+++ b/v2v/lib_esx.mli
@@ -18,6 +18,6 @@
 
 (** Functions for dealing with ESX. *)
 
-val map_path_to_uri : Xml.uri -> string -> string option -> string * string option
+val map_path_to_uri : bool -> Xml.uri -> string -> string -> string -> string option -> string * string option
 (** Map a VMware path like "[datastore1] guest/guest.vmdk" to the
     URL where we can fetch the data. *)
diff --git a/v2v/utils.ml b/v2v/utils.ml
index dd46835..38443fa 100644
--- a/v2v/utils.ml
+++ b/v2v/utils.ml
@@ -61,6 +61,19 @@ let uri_quote str =
   done;
   String.concat "" (List.rev !xs)
 
+(* JSON quoting. *)
+let json_quote str =
+  let str = replace_str str "\\" "\\\\" in
+  let str = replace_str str "\"" "\\\"" in
+  let str = replace_str str "'" "\\'" in
+  let str = replace_str str "\008" "\\b" in
+  let str = replace_str str "\012" "\\f" in
+  let str = replace_str str "\n" "\\n" in
+  let str = replace_str str "\r" "\\r" in
+  let str = replace_str str "\t" "\\t" in
+  let str = replace_str str "\011" "\\v" in
+  str
+
 external drive_name : int -> string = "v2v_utils_drive_name"
 
 let compare_app2_versions app1 app2 =
diff --git a/v2v/virt-v2v.pod b/v2v/virt-v2v.pod
index 5b3393e..90160ea 100644
--- a/v2v/virt-v2v.pod
+++ b/v2v/virt-v2v.pod
@@ -4,9 +4,9 @@ virt-v2v - Convert a guest to use KVM
 
 =head1 SYNOPSIS
 
- virt-v2v -ic esx://esx.example.com/ esx_guest
+ virt-v2v -ic vpx://esx.example.com/Datacenter/esxi esx_guest
 
- virt-v2v -ic esx://esx.example.com/ \
+ virt-v2v -ic vpx://esx.example.com/Datacenter/esxi \
    -o rhev -os rhev.nfs:/export_domain --network rhevm esx_guest
 
  virt-v2v -i libvirtxml -o local -os /var/tmp guest-domain.xml
@@ -63,17 +63,21 @@ I<-o rhev> is used to write to a RHEV-M / oVirt target.
 
 =head1 EXAMPLES
 
-=head2 Convert from VMware ESX server to local libvirt
+=head2 Convert from VMware vCenter server to local libvirt
 
-You have a VMware ESX server called C<esx.example.com>.  You want to
-convert a guest called C<esx_guest> to run locally under libvirt.
+You have a VMware vCenter server called C<esx.example.com>, a
+datacenter called C<Datacenter>, and an ESXi hypervisor called
+C<esxi>.  You want to convert a guest called C<esx_guest> to run
+locally under libvirt.
 
- virt-v2v -ic esx://example.com esx_guest
+ virt-v2v -ic vpx://example.com/Datacenter/esxi esx_guest
 
 In this case you will most likely have to run virt-v2v as C<root>,
 since it needs to talk to the system libvirt daemon and copy the guest
 disks to C</var/lib/libvirt/images>.
 
+For more information see L</INPUT FROM VMWARE VCENTER SERVER> below.
+
 =head2 Convert from ESX to RHEV-M/oVirt
 
 This is the same as the previous example, except you want to send the
@@ -83,7 +87,7 @@ the location of the Export Storage Domain you should check the
 settings on your RHEV-M management console.  Guest network
 interface(s) are connected to the target network called C<rhevm>.
 
- virt-v2v -ic esx://esx.example.com/ \
+ virt-v2v -ic vpx://esx.example.com/Datacenter/esxi \
    -o rhev -os rhev.nfs:/export_domain --network rhevm esx_guest
 
 In this case the host running virt-v2v acts as a B<conversion server>.
@@ -169,6 +173,8 @@ is only used when S<I<-i libvirt>>.
 Only local libvirt connections and ESX connections can be used.
 Remote libvirt connections will not work in general.
 
+See also L</INPUT FROM VMWARE VCENTER SERVER> below.
+
 =item B<-if> format
 
 For I<-i disk> only, this specifies the format of the input disk
@@ -530,6 +536,76 @@ I<--bridge> option instead.  For example:
  
  $ virt-v2v [...] --bridge br0:targetbr
 
+=head1 INPUT FROM VMWARE VCENTER SERVER
+
+Virt-v2v is able to import guests from VMware vCenter Server.
+
+Note that virt-v2v B<cannot> import guests directly from an ESXi
+hypervisor.
+
+Virt-v2v uses libvirt for access to vCenter, and therefore the input
+mode should be I<-i libvirt>.  As this is the default, you don't need
+to specify it on the command line.
+
+The libvirt URI of a vCenter server looks something like this:
+
+ vpx://user@server/Datacenter/esxi
+
+where C<user@> is the (optional, but recommended) user to connect as,
+C<server> is the vCenter Server (I<not> hypervisor), C<Datacenter> is
+the name of the datacenter, and C<esxi> is the name of the ESXi
+hypervisor running the guest.
+
+For full details of libvirt URIs, see: L<http://libvirt.org/drvesx.html>
+
+=head2 ESX: TEST LIBVIRT CONNECTION TO VCENTER
+
+Use the L<virsh(1)> command to list the guests on the vCenter Server
+like this:
+
+ $ virsh -c 'vpx://root@esx.example.com/Datacenter/esxi' list --all
+ Enter root's password for esx.example.com: ***
+ 
+  Id    Name                           State
+ ----------------------------------------------------
+  -     Fedora 20                      shut off
+  -     Windows 2003                   shut off
+
+If you get an error "Peer certificate cannot be authenticated with
+given CA certificates" or similar, then you can either import the ESX
+host's certificate, or bypass signature verification by adding the
+C<?no_verify=1> flag:
+
+ $ virsh -c 'vpx://root@esx.example.com/Datacenter/esxi?no_verify=1' list --all
+
+You should also try dumping the metadata from any guest on your
+server, like this:
+
+ $ virsh -c 'vpx://root@esx.example.com/Datacenter/esxi' dumpxml "Windows 2003"
+ <domain type='vmware'>
+   <name>Windows 2003</name>
+   [...]
+ </domain>
+
+B<If the above commands do not work, then virt-v2v is not going to
+work either>.  Fix your libvirt configuration and/or your VMware
+vCenter Server before continuing.
+
+=head2 ESX: IMPORTING A GUEST
+
+To import a particular guest from vCenter Server, do:
+
+ $ virt-v2v -ic 'esx://root@esx.example.com/Datacenter/esxi?no_verify=1' \
+   "Windows 2003" \
+   -o local -os /var/tmp
+
+where C<Windows 2003> is the name of the guest (which must be shut
+down).
+
+In this case the output flags are set to write the converted guest to
+a temporary directory as this is just an example, but you can also
+write to libvirt or RHEV.
+
 =head1 OUTPUT TO LIBVIRT
 
 The I<-o libvirt> option lets you upload the converted guest to

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