Blob Blame History Raw
From f00bde700faf0708c8af440efdcc6b48e93f3ce4 Mon Sep 17 00:00:00 2001
From: "Richard W.M. Jones" <rjones@redhat.com>
Date: Fri, 13 Oct 2017 16:30:16 +0100
Subject: [PATCH] v2v: vCenter: Refactor the API to this module.

This module had a selection of functions taking a different mix of
parameters and doing slightly different things.  You could call one
function to return an https://... URL, or another function to return a
qemu URL, and there was a third function to get the session cookie but
you generally had to call that anyway (and it was implicitly called
when making the qemu URL!)

Integrate these into a single function which returns a struct
returning all possible values.

This is conceptually refactoring, except that the session cookie is no
longer memoized, but we didn't use this feature (of calling
get_session_cookie multiple times) anyway.

(cherry picked from commit fb79fcde2947ff7e73f96343e8311e43b22a6f66)
---
 v2v/copy_to_local.ml               |  12 +-
 v2v/input_libvirt_vcenter_https.ml |  12 +-
 v2v/vCenter.ml                     | 319 +++++++++++++++++++------------------
 v2v/vCenter.mli                    |  66 +++++---
 4 files changed, 211 insertions(+), 198 deletions(-)

diff --git a/v2v/copy_to_local.ml b/v2v/copy_to_local.ml
index ca5578f3f..5fb1b79ff 100644
--- a/v2v/copy_to_local.ml
+++ b/v2v/copy_to_local.ml
@@ -151,14 +151,10 @@ read the man page virt-v2v-copy-to-local(1).
             error (f_"vcenter: <vmware:datacenterpath> was not found in the XML.  You need to upgrade to libvirt ≥ 1.2.20.") in
        List.map (
          fun (remote_disk, local_disk) ->
-           let url, sslverify =
-             VCenter.map_source_to_https dcpath parsed_uri
-                                         server remote_disk in
-           debug "esxi: source disk %s (sslverify=%b)" url sslverify;
-           let cookie =
-             VCenter.get_session_cookie password "esx"
-                                        parsed_uri sslverify url in
-           (url, local_disk, sslverify, cookie)
+           let { VCenter.https_url; sslverify; session_cookie } =
+             VCenter.map_source dcpath parsed_uri "esx" server remote_disk in
+           debug "esxi: source disk %s (sslverify=%b)" https_url sslverify;
+           (https_url, local_disk, sslverify, session_cookie)
        ) disks
     | Test | Xen_ssh _ ->
        List.map (fun (remote_disk, local_disk) ->
diff --git a/v2v/input_libvirt_vcenter_https.ml b/v2v/input_libvirt_vcenter_https.ml
index 497caca4f..1153e74a3 100644
--- a/v2v/input_libvirt_vcenter_https.ml
+++ b/v2v/input_libvirt_vcenter_https.ml
@@ -102,9 +102,9 @@ object
       | { p_source = P_source_dev _ } -> assert false
       | { p_source_disk = disk; p_source = P_dont_rewrite } -> disk
       | { p_source_disk = disk; p_source = P_source_file path } ->
-        let qemu_uri =
-          VCenter.map_source_to_uri readahead dcPath password
-                                    parsed_uri scheme server path in
+        let { VCenter.qemu_uri } =
+          VCenter.map_source ?readahead ?password
+                             dcPath parsed_uri scheme server path 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.
@@ -123,9 +123,9 @@ object
     | None -> ()
     | Some orig_path ->
       let readahead = readahead_for_copying in
-      let backing_qemu_uri =
-        VCenter.map_source_to_uri readahead dcPath password
-                                  parsed_uri scheme server orig_path in
+      let { VCenter.qemu_uri = backing_qemu_uri } =
+        VCenter.map_source ?readahead ?password
+                           dcPath parsed_uri scheme server orig_path in
 
       (* Rebase the qcow2 overlay to adjust the readahead parameter. *)
       let cmd = [ "qemu-img"; "rebase"; "-u"; "-b"; backing_qemu_uri;
diff --git a/v2v/vCenter.ml b/v2v/vCenter.ml
index 2f7e32ad6..341a40b25 100644
--- a/v2v/vCenter.ml
+++ b/v2v/vCenter.ml
@@ -38,167 +38,168 @@ let uri_quote str =
   done;
   String.concat "" (List.rev !xs)
 
-(* Memoized session cookie. *)
-let session_cookie = ref ""
-
-let get_session_cookie password scheme uri sslverify url =
-  if !session_cookie <> "" then
-    Some !session_cookie
-  else (
-    let curl_args = ref [
-      "head", None;
-      "silent", None;
-      "url", Some url;
-    ] in
-    (match uri.uri_user, password with
-     | None, None -> ()
-     | None, Some _ ->
-        warning (f_"--password-file parameter ignored because 'user@' was not given in the URL")
-     | Some user, None ->
-        push_back curl_args ("user", Some user)
-     | Some user, Some password ->
-        push_back curl_args ("user", Some (user ^ ":" ^ password))
-    );
-    if not sslverify then push_back curl_args ("insecure", None);
-
-    let curl_h = Curl.create !curl_args in
-    let lines = Curl.run curl_h in
-
-    let dump_response chan =
-      Curl.print chan curl_h;
-
-      (* Dump out the output of the command. *)
-      List.iter (fun x -> fprintf chan "%s\n" x) lines;
-      flush chan
-    in
-
-    if verbose () then dump_response stderr;
-
-    (* 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_"vcenter: no status code in output of 'curl' command.  Is 'curl' installed?")
-    );
-
-    if status = "401" then (
-      dump_response stderr;
-      if uri.uri_user <> None then
-        error (f_"vcenter: incorrect username or password")
-      else
-        error (f_"vcenter: incorrect username or password.  You might need to specify the username in the URI like this: %s://USERNAME@[etc]")
-              scheme
-    );
-
-    if status = "404" then (
-      dump_response stderr;
-      error (f_"vcenter: URL not found: %s") url
-    );
-
-    if status <> "200" then (
-      dump_response stderr;
-      error (f_"vcenter: 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 (f_"vcenter: 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
-  )
+type remote_resource = {
+  https_url : string;
+  qemu_uri : string;
+  session_cookie : string option;
+  sslverify : bool;
+}
 
 let source_re = Str.regexp "^\\[\\(.*\\)\\] \\(.*\\)\\.vmdk$"
 
-let map_source_to_https dcPath uri server path =
-  if not (Str.string_match source_re path 0) then
-    (path, true)
-  else (
-    let datastore = Str.matched_group 1 path
-    and path = Str.matched_group 2 path in
-
-    let port =
-      match uri.uri_port with
-      | 443 -> ""
-      | n when n >= 1 -> ":" ^ string_of_int n
-      | _ -> "" in
-
-    (* XXX Old virt-v2v could also handle snapshots, ie:
-     * "[datastore1] Fedora 20/Fedora 20-NNNNNN.vmdk"
-     * XXX Need to handle templates.  The file is called "-delta.vmdk" in
-     * place of "-flat.vmdk".
-     *)
-    let url =
-      sprintf
-        "https://%s%s/folder/%s-flat.vmdk?dcPath=%s&dsName=%s"
-        server port
-        (uri_quote path) (uri_quote dcPath) (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
-
-    (url, sslverify)
-  )
-
-let map_source_to_uri readahead dcPath password uri scheme server path =
-  let url, sslverify = map_source_to_https dcPath uri server path in
-
-  (* Now we have to query the server to get the session cookie. *)
-  let session_cookie = get_session_cookie password scheme uri sslverify url in
-
-  (* Construct the JSON parameters. *)
-  let json_params = [
-    "file.driver", JSON.String "https";
-    "file.url", JSON.String url;
-    (* https://bugzilla.redhat.com/show_bug.cgi?id=1146007#c10 *)
-    "file.timeout", JSON.Int 2000;
-  ] in
-
-  let json_params =
-    match readahead with
-    | None -> json_params
-    | Some readahead ->
-       ("file.readahead", JSON.Int readahead) :: json_params in
-
-  let json_params =
-    if sslverify then json_params
-    else ("file.sslverify", JSON.String "off") :: json_params in
-
-  let json_params =
-    match session_cookie with
-    | None -> json_params
-    | Some cookie -> ("file.cookie", JSON.String cookie) :: json_params in
-
-  debug "vcenter: json parameters: %s" (JSON.string_of_doc json_params);
-
-  (* Turn the JSON parameters into a 'json:' protocol string.
-   * Note this requires qemu-img >= 2.1.0.
+let rec map_source ?readahead ?password dcPath uri scheme server path =
+  (* If no_verify=1 was passed in the libvirt URI, then we have to
+   * turn off certificate verification here too.
    *)
-  let qemu_uri = "json: " ^ JSON.string_of_doc json_params in
+  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
 
-  qemu_uri
+  let https_url =
+    if not (Str.string_match source_re path 0) then
+      path
+    else (
+      let datastore = Str.matched_group 1 path
+      and path = Str.matched_group 2 path in
+
+      let port =
+        match uri.uri_port with
+        | 443 -> ""
+        | n when n >= 1 -> ":" ^ string_of_int n
+        | _ -> "" in
+
+      (* XXX Old virt-v2v could also handle snapshots, ie:
+       * "[datastore1] Fedora 20/Fedora 20-NNNNNN.vmdk"
+       * XXX Need to handle templates.  The file is called "-delta.vmdk" in
+       * place of "-flat.vmdk".
+       *)
+      sprintf "https://%s%s/folder/%s-flat.vmdk?dcPath=%s&dsName=%s"
+              server port
+              (uri_quote path) (uri_quote dcPath) (uri_quote datastore)
+    ) in
+
+  let session_cookie =
+    get_session_cookie password scheme uri sslverify https_url in
+
+  let qemu_uri =
+    (* Construct the JSON parameters for the qemu URI. *)
+    let json_params = [
+      "file.driver", JSON.String "https";
+      "file.url", JSON.String https_url;
+      (* https://bugzilla.redhat.com/show_bug.cgi?id=1146007#c10 *)
+      "file.timeout", JSON.Int 2000;
+    ] in
+
+    let json_params =
+      match readahead with
+      | None -> json_params
+      | Some readahead ->
+         ("file.readahead", JSON.Int readahead) :: json_params in
+
+    let json_params =
+      if sslverify then json_params
+      else ("file.sslverify", JSON.String "off") :: json_params in
+
+    let json_params =
+      match session_cookie with
+      | None -> json_params
+      | Some cookie -> ("file.cookie", JSON.String cookie) :: json_params in
+
+    debug "vcenter: json parameters: %s" (JSON.string_of_doc json_params);
+
+    (* Turn the JSON parameters into a 'json:' protocol string.
+     * Note this requires qemu-img >= 2.1.0.
+     *)
+    let qemu_uri = "json: " ^ JSON.string_of_doc json_params in
+
+    qemu_uri in
+
+  (* Return the struct. *)
+  { https_url = https_url;
+    qemu_uri = qemu_uri;
+    session_cookie = session_cookie;
+    sslverify = sslverify }
+
+and get_session_cookie password scheme uri sslverify https_url =
+  let curl_args = ref [
+    "head", None;
+    "silent", None;
+    "url", Some https_url;
+  ] in
+  (match uri.uri_user, password with
+   | None, None -> ()
+   | None, Some _ ->
+      warning (f_"--password-file parameter ignored because 'user@' was not given in the URL")
+   | Some user, None ->
+      push_back curl_args ("user", Some user)
+   | Some user, Some password ->
+      push_back curl_args ("user", Some (user ^ ":" ^ password))
+  );
+  if not sslverify then push_back curl_args ("insecure", None);
+
+  let curl_h = Curl.create !curl_args in
+  let lines = Curl.run curl_h in
+
+  let dump_response chan =
+    Curl.print chan curl_h;
+
+    (* Dump out the output of the command. *)
+    List.iter (fun x -> fprintf chan "%s\n" x) lines;
+    flush chan
+  in
+
+  if verbose () then dump_response stderr;
+
+  (* 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_"vcenter: no status code in output of ‘curl’ command.  Is ‘curl’ installed?")
+  );
+
+  if status = "401" then (
+    dump_response stderr;
+    if uri.uri_user <> None then
+      error (f_"vcenter: incorrect username or password")
+    else
+      error (f_"vcenter: incorrect username or password.  You might need to specify the username in the URI like this: %s://USERNAME@[etc]")
+            scheme
+  );
+
+  if status = "404" then (
+    dump_response stderr;
+    error (f_"vcenter: URL not found: %s") https_url
+  );
+
+  if status <> "200" then (
+    dump_response stderr;
+    error (f_"vcenter: invalid response from server")
+  );
+
+  (* Get the cookie. *)
+  let rec loop = function
+    | [] ->
+       dump_response stderr;
+       warning (f_"vcenter: could not read session cookie from the vCenter Server, conversion may consume all sessions on the server and fail part way through");
+       None
+    | line :: lines ->
+       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
+         Some cookie
+       )
+       else
+         loop lines
+  in
+  loop lines
diff --git a/v2v/vCenter.mli b/v2v/vCenter.mli
index 55d70b486..03749966f 100644
--- a/v2v/vCenter.mli
+++ b/v2v/vCenter.mli
@@ -18,35 +18,51 @@
 
 (** Functions for dealing with VMware vCenter. *)
 
-val get_session_cookie : string option -> string -> Xml.uri -> bool -> string -> string option
-(** [get_session_cookie password scheme uri sslverify url]
-    contacts the vCenter server, logs in, and gets the session cookie,
-    which can later be passed back to the server instead of having to
-    log in each time (this is also more efficient since it avoids
-    vCenter running out of authentication sessions).
+type remote_resource = {
+  https_url : string;
+  (** The full URL of the remote disk as an https link on the vCenter
+      server.  It will have the general form
+      [https://vcenter/folder/.../guest-flat.vmdk?dcPath=...&...] *)
 
-    Returns [None] if the session cookie could not be read (but
-    authentication was successful).  You can proceed without the
-    session cookie in this case, but there is an unavoidable
-    danger of running out of authentication sessions.  If the
-    session cookie could not be read, this function prints a
-    warning.
+  qemu_uri : string;
+  (** The remote disk as a QEMU URI.  This opaque blob (usually a
+      [json:] URL) can be passed to [qemu] or [qemu-img] as a backing
+      file. *)
 
-    The session cookie is memoized so you can call this function as
-    often as you want, and only a single log in is made. *)
+  session_cookie : string option;
+  (** When creating the URLs above, the module contacts the vCenter
+      server, logs in, and gets the session cookie, which can later
+      be passed back to the server instead of having to log in each
+      time (this is also more efficient since it avoids vCenter
+      running out of authentication sessions).
 
-val map_source_to_uri : int option -> string -> string option -> Xml.uri -> string -> string -> string -> string
-(** [map_source_to_uri readahead dcPath password uri scheme server path]
-    maps the [<source path=...>] string to a qemu URI.
+      This can be [None] if the session cookie could not be read (but
+      authentication was successful).  You can proceed without the
+      session cookie in this case, but there is an unavoidable
+      danger of running out of authentication sessions.  If the
+      session cookie could not be read, this function prints a
+      warning.
 
-    The [path] will be something like:
+      If authentication {i failed} then the {!map_source} function
+      would exit with an error, so [None] does not indicate auth
+      failure. *)
 
+  sslverify : bool;
+  (** This is true except when the libvirt URI had [?no_verify=1] in
+      the parameters. *)
+}
+(** The "remote resource" is the structure returned by the {!map_source}
+    function. *)
+
+val map_source : ?readahead:int -> ?password:string -> string -> Xml.uri -> string -> string -> string -> remote_resource
+(** [map_source ?readahead ?password dcPath uri scheme server path]
+    maps the [<source path=...>] string to a {!remote_resource}
+    structure containing both an [https://] URL and a qemu URI,
+    both pointing the guest disk.
+
+    The input [path] comes from libvirt and will be something like:
     ["[datastore1] Fedora 20/Fedora 20.vmdk"]
+    (including those literal spaces in the string).
 
-    including those literal spaces in the string. *)
-
-val map_source_to_https : string -> Xml.uri -> string -> string -> string * bool
-(** [map_source_to_https dcPath uri server path] is the same as
-    {!map_source_to_uri} but it produces a regular [https://...] URL.
-    The returned boolean is whether TLS certificate verification
-    should be done. *)
+    This checks that the disk exists and that authentication is
+    correct, otherwise it will fail. *)
-- 
2.14.3