Blame SOURCES/0049-v2v-vCenter-Refactor-the-API-to-this-module.patch

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