Files
libguestfs/v2v/output_openstack.ml
Richard W.M. Jones a4f83ee6a4 v2v: -o openstack: -oo verify-server-certificate=(true|false) (RHBZ#1651432).
If ‘virt-v2v -oo verify-server-certificate=false’ is used then
‘openstack --insecure’ flag is added whenever we invoke the openstack
command.  This turns off SSL certificate validation.  The default is
to verify the server certificate (which is the default of the
openstack command).
2018-11-20 16:14:09 +00:00

491 lines
16 KiB
OCaml
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
(* virt-v2v
* Copyright (C) 2009-2018 Red Hat Inc.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*)
open Printf
open Unix
open Std_utils
open Tools_utils
open Unix_utils
open JSON_parser
open Common_gettext.Gettext
open Types
open Utils
(* Timeout waiting for new Cinder volumes to move to "available" state.
* We assume this could be quite a long time on backends which want
* to preallocate the storage.
*)
let available_timeout = 300 (* seconds *)
(* Timeout waiting for Cinder volumes to attach to the appliance. *)
let attach_timeout = 60 (* seconds *)
(* The -oo options supported by this output method. *)
type os_options = {
(* The server name or UUID of the conversion appliance where
* virt-v2v is currently running. In future we may be able
* to make this optional and derive it from the OpenStack
* metadata service instead.
*)
server_id : string;
(* All other OpenStack parameters, passed through unmodified
* on the openstack command line.
*)
authentication : string list;
(* If false, use the [openstack --insecure] switch (turns off SSL
* cert validation).
*)
verify_server_certificate : bool;
(* Optional guest_id which, if present, is saved as
* Cinder volume property virt_v2v_guest_id on every disk
* associated with this guest.
*)
guest_id : string option;
(* This setting is used by the test suite. *)
dev_disk_by_id : string option;
}
let print_output_options () =
printf (f_"virt-v2v -oo server-id=<NAME|UUID> [os-*=...]
Specify the name or UUID of the conversion appliance using
virt-v2v ... -o openstack -oo server-id=<NAME|UUID>
When virt-v2v runs it will attach the Cinder volumes to the
conversion appliance, so this name or UUID must be the name
of the virtual machine on OpenStack where virt-v2v is running.
In addition, all usual OpenStack “os-*” parameters or “OS_*”
environment variables can be used.
Openstack “--os-*” parameters must be written as “virt-v2v -oo os-*”.
For example:
virt-v2v -oo os-username=<NAME>
equivalent to openstack: --os-username=<NAME>
or the environment variable: OS_USERNAME=<NAME>
virt-v2v -oo os-project-name=<NAME>
equivalent to openstack: --os-project-name=<NAME>
or the environment variable: OS_PROJECT_NAME=<NAME>
The os-* parameters and environment variables are optional.
")
let parse_output_options options =
let server_id = ref None in
let dev_disk_by_id = ref None in
let verify_server_certificate = ref true in
let guest_id = ref None in
let authentication = ref [] in
List.iter (
function
| "server-id", v ->
server_id := Some v
| "dev-disk-by-id", v ->
dev_disk_by_id := Some v
| "verify-server-certificate", "" ->
verify_server_certificate := true
| "verify-server-certificate", v ->
verify_server_certificate := bool_of_string v
| "guest-id", v ->
guest_id := Some v
| k, v when String.is_prefix k "os-" ->
(* Accumulate any remaining/unknown -oo os-* parameters
* into the authentication list, where they will be
* pass unmodified through to the openstack command.
*)
let opt = sprintf "--%s=%s" k v in
authentication := opt :: !authentication
| k, _ ->
error (f_"-o openstack: unknown output option -oo %s") k
) options;
let server_id =
match !server_id with
| None ->
error (f_"openstack: -oo server-id=<NAME|UUID> not present");
| Some server_id -> server_id in
let authentication = List.rev !authentication in
let verify_server_certificate = !verify_server_certificate in
let guest_id = !guest_id in
let dev_disk_by_id = !dev_disk_by_id in
{ server_id; authentication; verify_server_certificate;
guest_id; dev_disk_by_id }
(* UTC conversion time. *)
let iso_time =
let time = time () in
let tm = gmtime time in
sprintf "%04d/%02d/%02d %02d:%02d:%02d"
(tm.tm_year + 1900) (tm.tm_mon + 1) tm.tm_mday
tm.tm_hour tm.tm_min tm.tm_sec
class output_openstack output_conn output_password output_storage
(os_options : os_options) =
(* The extra command line parameters derived from -oo etc. *)
let extra_args =
let args = ref os_options.authentication in
Option.may (fun oc -> List.push_back args (sprintf "--os-auth-url=%s" oc))
output_conn;
if not os_options.verify_server_certificate then
List.push_back args "--insecure";
!args in
(* We use this convenient wrapper around [Tools_utils.run_command]
* for two reasons: (1) Because we want to run openstack with
* extra_args. (2) OpenStack commands are noisy so we want to
* direct stdout to /dev/null unless we're in verbose mode.
*)
let run_openstack_command args =
let cmd = [ "openstack" ] @ extra_args @ args in
let stdout_fd =
if verbose () then None
else Some (openfile "/dev/null" [O_WRONLY] 0) in
(* Note that run_command will close stdout_fd if defined. *)
Tools_utils.run_command ?stdout_fd cmd
in
(* Similar to above, run the openstack command and capture the
* JSON document printed by the command. Note you must add
* '-f json' to the args yourself.
*)
let run_openstack_command_capture_json args =
let cmd = [ "openstack" ] @ extra_args @ args in
let json, chan = Filename.open_temp_file "v2vopenstack" ".json" in
unlink_on_exit json;
let fd = descr_of_out_channel chan in
(* Note that Tools_utils.run_command closes fd. *)
if Tools_utils.run_command ~stdout_fd:fd cmd <> 0 then
None
else (
let json = json_parser_tree_parse_file json in
debug "openstack: JSON parsed as: %s"
(JSON.string_of_doc ~fmt:JSON.Indented ["", json]);
Some json
)
in
(* Create a new Cinder volume and wait for its status to change to
* "available". Returns the volume id.
*)
let create_cinder_volume name description size =
(* Cinder volumes are allocated in increments of 1 GB. Weird. *)
let size_gb =
let s = roundup64 size 1073741824L in
let s = s /^ 1073741824L in
Int64.to_string s in
let args = ref [] in
List.push_back_list args [ "volume"; "create";
"-f"; "json";
"--size"; size_gb;
"--description"; description;
"--non-bootable";
"--read-write" ];
Option.may (
fun os ->
List.push_back_list args [ "--type"; os ]
) output_storage;
List.push_back args name;
let json =
match run_openstack_command_capture_json !args with
| None ->
error (f_"openstack: failed to create a cinder volume, see earlier error messages")
| Some json -> json in
let id = object_get_string "id" json in
(* Wait for the volume state to change to "available". *)
let args = [ "volume"; "show"; "-f"; "json"; id ] in
with_timeout
(s_"wait for cinder volume status to change to \"available\"")
available_timeout
(fun () ->
match run_openstack_command_capture_json args with
| None ->
error (f_"openstack: failed to query cinder volume status, see earlier error messages")
| Some json ->
match object_get_string "status" json with
| "creating" -> None
| "available" -> Some () (* done *)
| status ->
error (f_"openstack: unknown volume status \"%s\": expected \"creating\" or \"available\"") status
);
id
in
(* Delete a cinder volume.
*
* This ignores errors since the only time we are doing this is on
* the failure path.
*)
let delete_cinder_volume id =
let args = [ "volume"; "delete"; id ] in
ignore (run_openstack_command args)
in
(* Update metadata on a cinder volume. *)
let update_cinder_volume_metadata ?bootable ?description
?(image_properties = [])
?(volume_properties = [])
id =
let args = ref [ "volume"; "set" ] in
Option.may (
fun bootable ->
List.push_back args
(if bootable then "--bootable" else "--non-bootable")
) bootable;
Option.may (
fun description ->
List.push_back_list args ["--description"; description]
) description;
let image_properties =
List.flatten (
List.map (
fun (k, v) -> [ "--image-property"; sprintf "%s=%s" k v ]
) image_properties
) in
List.push_back_list args image_properties;
let volume_properties =
List.flatten (
List.map (
fun (k, v) -> [ "--property"; sprintf "%s=%s" k v ]
) volume_properties
) in
List.push_back_list args volume_properties;
List.push_back args id;
if run_openstack_command !args <> 0 then
error (f_"openstack: failed to set image properties on cinder volume, see earlier error messages")
in
(* Attach volume to current VM and wait for it to appear.
* Returns the block device name.
*)
let attach_volume id =
let args = [ "server"; "add"; "volume";
os_options.server_id; id ] in
if run_openstack_command args <> 0 then
error (f_"openstack: failed to attach cinder volume to VM, see earlier error messages");
(* We expect the disk to appear under /dev/disk/by-id.
*
* In theory the serial number of the disk should be the
* volume ID. However the practical reality is:
*
* (1) Only the first 20 characters are included by OpenStack.
* (2) udev(?) adds extra stuff
*
* So look for any file under /dev/disk/by-id which contains
* the prefix of the volume ID as a substring.
*)
let dev_disk_by_id =
Option.default "/dev/disk/by-id" os_options.dev_disk_by_id in
let prefix_len = 16 (* maybe 20, but be safe *) in
let prefix_id =
if String.length id > prefix_len then String.sub id 0 prefix_len
else id in
with_timeout
(sprintf (f_"waiting for cinder volume %s to attach to the conversion appliance") id)
attach_timeout
(fun () ->
let entries =
try Sys.readdir dev_disk_by_id
(* It's possible for /dev/disk/by-id to not exist, since it's
* only created by udev on demand, so ignore this error.
*)
with Sys_error _ -> [||] in
let entries = Array.to_list entries in
let entries =
List.filter (fun e -> String.find e prefix_id >= 0) entries in
match entries with
| d :: _ -> Some (dev_disk_by_id // d)
| [] -> None
);
in
(* Detach volume from current VM. This does not wait and doesn't
* check for errors, since either we're on the failure path and/or
* there's nothing we could do with the error anyway.
*)
let detach_volume id =
let args = [ "server"; "remove"; "volume";
os_options.server_id; id ] in
ignore (run_openstack_command args)
in
object
inherit output
method precheck () =
(* Run the openstack command simply to check we can connect
* with the provided authentication parameters/environment
* variables. Issuing a token should have only a tiny
* overhead.
*)
let args = [ "token"; "issue" ] in
if run_openstack_command args <> 0 then
error (f_"openstack: precheck failed, there may be a problem with authentication, see earlier error messages")
method as_options =
"-o openstack" ^
(match output_conn with
| None -> ""
| Some oc -> " -oc " ^ oc) ^
(match output_password with
| None -> ""
| Some op -> " -op " ^ op)
method supported_firmware = [ TargetBIOS ]
(* List of Cinder volume IDs. *)
val mutable volume_ids = []
(* If we didn't finish successfully, delete on exit. *)
val mutable delete_volumes_on_exit = true
(* Create the Cinder volumes, wait for them to attach to the
* appliance, and return the paths of the /dev devices.
*)
method prepare_targets source overlays
target_buses guestcaps inspect target_firmware =
(* Set up an at-exit handler so we:
* (1) Unconditionally detach volumes.
* (2) Delete the volumes, but only if conversion was not successful.
*)
at_exit (
fun () ->
List.iter detach_volume volume_ids;
if delete_volumes_on_exit then (
(* XXX We probably need to wait for the previous
* detach operation to complete - unclear how.
*)
List.iter delete_cinder_volume volume_ids;
volume_ids <- []
)
);
(* Set a known description for volumes, then change it later
* when conversion is successful. In theory this would allow
* some kind of garbage collection for unfinished conversions
* in the case that virt-v2v crashes.
*)
let description =
sprintf "virt-v2v temporary volume for %s" source.s_name in
(* Create the Cinder volumes. *)
List.iter (
fun (_, ov) ->
(* Unclear what we should set the name to, so just make
* something related to the guest name. Cinder volume
* names do not need to be unique.
*)
let name = sprintf "%s-%s" source.s_name ov.ov_sd in
(* Create the cinder volume and add the returned volume
* ID to the volume_ids list.
*)
let id = create_cinder_volume name description ov.ov_virtual_size in
volume_ids <- volume_ids @ [id]
) overlays;
(* Attach volume IDs to the conversion appliance and wait
* for the device nodes to appear.
*)
List.map (
fun id ->
let dev = attach_volume id in
TargetFile dev
) volume_ids
method create_metadata source targets
target_buses guestcaps inspect target_firmware =
let nr_disks = List.length targets in
assert (nr_disks = List.length volume_ids);
assert (nr_disks >= 1);
(* Image properties are only set on the first disk.
*
* In addition we set the first disk to bootable
* (XXX see RHBZ#1308535 for why this is wrong).
*)
let image_properties =
Openstack_image_properties.create source target_buses
guestcaps inspect target_firmware in
update_cinder_volume_metadata ~bootable:true ~image_properties
(List.hd volume_ids);
(* For all disks we update the description to a "non-temporary"
* description (see above) and set volume properties.
*)
List.iteri (
fun i id ->
let description =
sprintf "%s disk %d/%d converted by virt-v2v"
source.s_name (i+1) nr_disks in
let volume_properties = ref [
"virt_v2v_version", Guestfs_config.package_version_full;
"virt_v2v_conversion_date", iso_time;
"virt_v2v_guest_name", source.s_name;
"virt_v2v_disk_index", sprintf "%d/%d" (i+1) nr_disks;
] in
(match source.s_genid with
| None -> ()
| Some genid ->
List.push_back volume_properties
("virt_v2v_vm_generation_id", genid)
);
(match os_options.guest_id with
| None -> ()
| Some guest_id ->
List.push_back volume_properties
("virt_v2v_guest_id", guest_id)
);
let volume_properties = !volume_properties in
update_cinder_volume_metadata ~description ~volume_properties id
) volume_ids;
(* Successful so don't delete on exit. *)
delete_volumes_on_exit <- false
end
let output_openstack = new output_openstack
let () = Modules_list.register_output_module "openstack"