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

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