From f00bde700faf0708c8af440efdcc6b48e93f3ce4 Mon Sep 17 00:00:00 2001 From: "Richard W.M. Jones" 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: 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 [] 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 [] 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