mirror of
https://github.com/libguestfs/libguestfs.git
synced 2026-03-21 22:53:37 +00:00
v2v: Add -o openstack target, writes to OpenStack & Cinder using APIs.
This commit is contained in:
9
TODO
9
TODO
@@ -597,3 +597,12 @@ virt-v2v -o rhv-upload
|
||||
That can't be fixed from the v2v side.
|
||||
|
||||
* There are unresolved issues about how to clean up disks on failure.
|
||||
|
||||
virt-v2v -o openstack
|
||||
---------------------
|
||||
|
||||
Use the metadata service to find the -oo server-id setting. It would
|
||||
no longer need to be specified on the command line. Note there are
|
||||
two variations of metadata service in OpenStack, either the config
|
||||
disk or link-local network address. We would need to support both, or
|
||||
the possibility that there is no metadata service.
|
||||
|
||||
@@ -70,6 +70,7 @@ SOURCES_MLI = \
|
||||
output_libvirt.mli \
|
||||
output_local.mli \
|
||||
output_null.mli \
|
||||
output_openstack.mli \
|
||||
output_qemu.mli \
|
||||
output_rhv.mli \
|
||||
output_rhv_upload.mli \
|
||||
@@ -138,6 +139,7 @@ SOURCES_ML = \
|
||||
output_rhv_upload_precheck_source.ml \
|
||||
output_rhv_upload.ml \
|
||||
output_vdsm.ml \
|
||||
output_openstack.ml \
|
||||
inspect_source.ml \
|
||||
target_bus_assignment.ml \
|
||||
measure_disk.ml \
|
||||
@@ -373,6 +375,7 @@ TESTS += \
|
||||
test-v2v-o-glance.sh \
|
||||
test-v2v-o-libvirt.sh \
|
||||
test-v2v-o-null.sh \
|
||||
test-v2v-o-openstack.sh \
|
||||
test-v2v-o-qemu.sh \
|
||||
test-v2v-o-rhv.sh \
|
||||
test-v2v-o-vdsm-options.sh \
|
||||
@@ -521,6 +524,7 @@ EXTRA_DIST += \
|
||||
test-v2v-o-glance.sh \
|
||||
test-v2v-o-libvirt.sh \
|
||||
test-v2v-o-null.sh \
|
||||
test-v2v-o-openstack.sh \
|
||||
test-v2v-o-qemu.sh \
|
||||
test-v2v-o-rhv.ovf.expected \
|
||||
test-v2v-o-rhv.sh \
|
||||
|
||||
@@ -138,6 +138,7 @@ let parse_cmdline () =
|
||||
| "libvirt" -> output_mode := `Libvirt
|
||||
| "disk" | "local" -> output_mode := `Local
|
||||
| "null" -> output_mode := `Null
|
||||
| "openstack" | "osp" | "rhosp" -> output_mode := `Openstack
|
||||
| "ovirt" | "rhv" | "rhev" -> output_mode := `RHV
|
||||
| "ovirt-upload" | "ovirt_upload" | "rhv-upload" | "rhv_upload" ->
|
||||
output_mode := `RHV_Upload
|
||||
@@ -412,6 +413,18 @@ read the man page virt-v2v(1).
|
||||
| `Null -> no_options (); `Null
|
||||
| `RHV -> no_options (); `RHV
|
||||
| `QEmu -> no_options (); `QEmu
|
||||
|
||||
| `Openstack ->
|
||||
if is_query then (
|
||||
Output_openstack.print_output_options ();
|
||||
exit 0
|
||||
)
|
||||
else (
|
||||
let os_options =
|
||||
Output_openstack.parse_output_options output_options in
|
||||
`Openstack os_options
|
||||
)
|
||||
|
||||
| `RHV_Upload ->
|
||||
if is_query then (
|
||||
Output_rhv_upload.print_output_options ();
|
||||
@@ -422,6 +435,7 @@ read the man page virt-v2v(1).
|
||||
Output_rhv_upload.parse_output_options output_options in
|
||||
`RHV_Upload rhv_options
|
||||
)
|
||||
|
||||
| `VDSM ->
|
||||
if is_query then (
|
||||
Output_vdsm.print_output_options ();
|
||||
@@ -578,6 +592,18 @@ read the man page virt-v2v(1).
|
||||
Output_qemu.output_qemu os qemu_boot,
|
||||
output_format, output_alloc
|
||||
|
||||
| `Openstack os_options ->
|
||||
if output_alloc <> Sparse then
|
||||
error_option_cannot_be_used_in_output_mode "openstack" "-oa";
|
||||
if output_format <> None then
|
||||
error_option_cannot_be_used_in_output_mode "openstack" "-of";
|
||||
if qemu_boot then
|
||||
error_option_cannot_be_used_in_output_mode "openstack" "--qemu-boot";
|
||||
Output_openstack.output_openstack output_conn output_password
|
||||
output_storage os_options,
|
||||
(* Force output format to raw sparse in -o openstack mode. *)
|
||||
Some "raw", Sparse
|
||||
|
||||
| `RHV ->
|
||||
if output_password <> None then
|
||||
error_option_cannot_be_used_in_output_mode "rhv" "-op";
|
||||
|
||||
474
v2v/output_openstack.ml
Normal file
474
v2v/output_openstack.ml
Normal file
@@ -0,0 +1,474 @@
|
||||
(* 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;
|
||||
|
||||
(* 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 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
|
||||
| "guest-id", v ->
|
||||
guest_id := Some v
|
||||
| k, v ->
|
||||
(* Accumulate any remaining/unknown -oo 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
|
||||
) 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 guest_id = !guest_id in
|
||||
let dev_disk_by_id = !dev_disk_by_id in
|
||||
{ server_id; authentication; 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;
|
||||
!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"
|
||||
32
v2v/output_openstack.mli
Normal file
32
v2v/output_openstack.mli
Normal file
@@ -0,0 +1,32 @@
|
||||
(* 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.
|
||||
*)
|
||||
|
||||
(** [-o openstack] target. *)
|
||||
|
||||
type os_options
|
||||
(** Miscellaneous extra command line parameters used by Openstack. *)
|
||||
|
||||
val print_output_options : unit -> unit
|
||||
val parse_output_options : (string * string) list -> os_options
|
||||
(** Print and parse openstack -oo options. *)
|
||||
|
||||
val output_openstack : string option -> string option -> string option ->
|
||||
os_options -> Types.output
|
||||
(** [output_openstack output_conn output_password output_storage os_options]
|
||||
creates and returns a new {!Types.output} object specialized for writing
|
||||
output to Openstack using the Openstack APIs. *)
|
||||
69
v2v/test-v2v-o-openstack.sh
Executable file
69
v2v/test-v2v-o-openstack.sh
Executable file
@@ -0,0 +1,69 @@
|
||||
#!/bin/bash -
|
||||
# libguestfs virt-v2v test script
|
||||
# Copyright (C) 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.
|
||||
|
||||
# Test -o openstack.
|
||||
|
||||
set -e
|
||||
|
||||
$TEST_FUNCTIONS
|
||||
skip_if_skipped
|
||||
skip_if_backend uml
|
||||
skip_unless_phony_guest windows.img
|
||||
|
||||
libvirt_uri="test://$abs_top_builddir/test-data/phony-guests/guests.xml"
|
||||
windows=$top_builddir/test-data/phony-guests/windows.img
|
||||
|
||||
export VIRT_TOOLS_DATA_DIR="$top_srcdir/test-data/fake-virt-tools"
|
||||
|
||||
d=test-v2v-o-openstack.d
|
||||
rm -rf $d
|
||||
mkdir $d
|
||||
|
||||
# We don't want to upload to the real openstack, so introduce a fake
|
||||
# openstack binary which just logs the command line and provides
|
||||
# JSON output where required.
|
||||
cat > $d/openstack <<'EOF'
|
||||
#!/bin/bash -
|
||||
echo "$@" >> test-v2v-o-openstack.d/log
|
||||
echo "$@" | grep -sq -- "-f json" && \
|
||||
echo '{ "id": "dummy-vol-id", "status": "available" }'
|
||||
exit 0
|
||||
EOF
|
||||
chmod +x $d/openstack
|
||||
export PATH=$(pwd)/$d:$PATH
|
||||
|
||||
# Create the dummy output volume which virt-v2v will write to.
|
||||
touch $d/dummy-vol-id
|
||||
|
||||
# Run virt-v2v -o openstack.
|
||||
$VG virt-v2v --debug-gc \
|
||||
-i libvirt -ic "$libvirt_uri" windows \
|
||||
-o openstack -on test \
|
||||
-oo server-id=test \
|
||||
-oo guest-id=guestid \
|
||||
-oo dev-disk-by-id=$d
|
||||
|
||||
# Check the log of openstack commands to make sure they look reasonable.
|
||||
grep 'token issue' $d/log
|
||||
grep 'volume create.*size 1.*temporary volume.*test-sda' $d/log
|
||||
grep 'server add volume' $d/log
|
||||
grep 'volume set.*--bootable.*dummy-vol-id' $d/log
|
||||
grep 'volume set.*--property.*virt_v2v_guest_id=guestid' $d/log
|
||||
grep 'server remove volume' $d/log
|
||||
|
||||
rm -r $d
|
||||
208
v2v/virt-v2v.pod
208
v2v/virt-v2v.pod
@@ -10,7 +10,7 @@ virt-v2v - Convert a guest to use KVM
|
||||
|
||||
virt-v2v -i disk disk.img -o local -os /var/tmp
|
||||
|
||||
virt-v2v -i disk disk.img -o glance
|
||||
virt-v2v -i disk disk.img -o openstack -oo server-id=v2v-vm
|
||||
|
||||
virt-v2v -ic vpx://vcenter.example.com/Datacenter/esxi vmware_guest \
|
||||
-o rhv-upload -oc https://ovirt-engine.example.com/ovirt-engine/api \
|
||||
@@ -85,14 +85,15 @@ as root in this case.
|
||||
For more information about converting from VMX files see
|
||||
L</INPUT FROM VMWARE VMX> below.
|
||||
|
||||
=head2 Convert disk image to OpenStack glance
|
||||
=head2 Convert disk image to OpenStack
|
||||
|
||||
Given a disk image from another hypervisor that you want to convert to
|
||||
run on OpenStack (only KVM-based OpenStack is supported), you can do:
|
||||
run on OpenStack (only KVM-based OpenStack is supported), you can run
|
||||
virt-v2v inside an OpenStack VM (called C<v2v-vm> below), and do:
|
||||
|
||||
virt-v2v -i disk disk.img -o glance
|
||||
virt-v2v -i disk disk.img -o openstack -oo server-id=v2v-vm
|
||||
|
||||
See L</OUTPUT TO GLANCE> below.
|
||||
See L</OUTPUT TO OPENSTACK> below.
|
||||
|
||||
=head2 Convert disk image to disk image
|
||||
|
||||
@@ -123,13 +124,14 @@ qemu, do:
|
||||
=head1 INPUT AND OUTPUT MODES
|
||||
|
||||
┌────────────┐ ┌─────────▶ -o null
|
||||
-i disk ────────────┐ │ │ ─┘┌───────▶ -o local
|
||||
-i disk ────────────┐ │ │ ─┘┌────────▶ -o local
|
||||
-i ova ──────────┐ └──▶ │ virt-v2v │ ──┘┌───────▶ -o qemu
|
||||
└────▶ │ conversion │ ───┘┌────────────┐
|
||||
VMware─▶┌────────────┐ │ server │ ────▶ -o libvirt │─▶ KVM
|
||||
Xen ───▶│ -i libvirt ──▶ │ │ │ (default) │
|
||||
... ───▶│ (default) │ │ │ ──┐ └────────────┘
|
||||
└────────────┘ │ │ ─┐└──────▶ -o glance
|
||||
... ───▶│ (default) │ │ │ ───┐└────────────┘
|
||||
└────────────┘ │ │ ──┐└───────▶ -o glance
|
||||
│ │ ─┐└────────▶ -o openstack
|
||||
-i libvirtxml ─────────▶ │ │ ┐├─────────▶ -o rhv
|
||||
-i vmx ────────────────▶ │ │ │└─────────▶ -o vdsm
|
||||
└────────────┘ └──────────▶ -o rhv-upload
|
||||
@@ -153,7 +155,8 @@ I<-i ova> is used for reading from a VMware ova source file.
|
||||
|
||||
I<-i vmx> is used for reading from a VMware vmx file.
|
||||
|
||||
I<-o glance> is used for writing to OpenStack Glance.
|
||||
I<-o openstack> is used for writing to OpenStack. I<-o glance> is a
|
||||
legacy option used for writing to OpenStack Glance.
|
||||
|
||||
I<-o libvirt> is used for writing to any libvirt target. Libvirt can
|
||||
connect to local or remote KVM hypervisors. The I<-oc> option selects
|
||||
@@ -222,7 +225,7 @@ QEMU and KVM only.
|
||||
|
||||
=over 4
|
||||
|
||||
=item OpenStack Glance
|
||||
=item OpenStack
|
||||
|
||||
=item Red Hat Virtualization (RHV) 4.1 and up
|
||||
|
||||
@@ -528,6 +531,9 @@ This is the same as I<-o local>.
|
||||
|
||||
=item B<-o> B<glance>
|
||||
|
||||
This is a legacy option. You should probably use I<-o openstack>
|
||||
instead.
|
||||
|
||||
Set the output method to OpenStack Glance. In this mode the converted
|
||||
guest is uploaded to Glance. See L</OUTPUT TO GLANCE> below.
|
||||
|
||||
@@ -566,6 +572,10 @@ The guest is converted and copied (unless you also specify
|
||||
I<--no-copy>), but the results are thrown away and no metadata is
|
||||
written.
|
||||
|
||||
=item B<-o> B<openstack>
|
||||
|
||||
Set the output method to OpenStack. See L</OUTPUT TO OPENSTACK> below.
|
||||
|
||||
=item B<-o> B<ovirt>
|
||||
|
||||
This is the same as I<-o rhv>.
|
||||
@@ -651,6 +661,18 @@ To display short help on what options are available you can use:
|
||||
|
||||
virt-v2v -o rhv-upload -oo "?"
|
||||
|
||||
=item B<-oo guest-id=>C<ID>
|
||||
|
||||
For I<-o openstack> (L</OUTPUT TO OPENSTACK>) only, set a guest ID
|
||||
which is saved on each Cinder volume in the C<virt_v2v_guest_id>
|
||||
volume property.
|
||||
|
||||
=item B<-oo os->*B<=>*
|
||||
|
||||
For I<-o openstack> (L</OUTPUT TO OPENSTACK>) only, set optional
|
||||
OpenStack authentication. For example I<-oo os-username=>NAME is
|
||||
equivalent to C<openstack --os-username=NAME>.
|
||||
|
||||
=item B<-oo rhv-cafile=>F<ca.pem>
|
||||
|
||||
For I<-o rhv-upload> (L</OUTPUT TO RHV>) only, the F<ca.pem> file
|
||||
@@ -677,6 +699,11 @@ For I<-o rhv-upload> (L</OUTPUT TO RHV>) only, verify the oVirt/RHV
|
||||
server’s identity by checking the server‘s certificate against the
|
||||
Certificate Authority.
|
||||
|
||||
=item B<-oo server-id=>C<NAME|UUID>
|
||||
|
||||
For I<-o openstack> (L</OUTPUT TO OPENSTACK>) only, set the name
|
||||
of the conversion appliance where virt-v2v is running.
|
||||
|
||||
=item B<-oo vdsm-compat=0.10>
|
||||
|
||||
=item B<-oo vdsm-compat=1.1>
|
||||
@@ -779,6 +806,8 @@ directory must exist.
|
||||
For I<-o rhv-upload>, this is the name of the destination Storage
|
||||
Domain.
|
||||
|
||||
For I<-o openstack>, this is the optional Cinder volume type.
|
||||
|
||||
For I<-o rhv>, this can be an NFS path of the Export Storage Domain
|
||||
of the form C<E<lt>hostE<gt>:E<lt>pathE<gt>>, eg:
|
||||
|
||||
@@ -2152,8 +2181,133 @@ enough like a RHV-M Export Storage Domain to trick virt-v2v:
|
||||
touch /tmp/rhv/$uuid/dom_md
|
||||
virt-v2v [...] -o rhv -os /tmp/rhv
|
||||
|
||||
=head1 OUTPUT TO OPENSTACK
|
||||
|
||||
To output to OpenStack, use the I<-o openstack> option.
|
||||
|
||||
=head2 OPENSTACK: SETTING UP A CONVERSION APPLIANCE
|
||||
|
||||
When virt-v2v is converting to OpenStack, it is unusual in that
|
||||
virt-v2v B<must> be running inside a virtual machine running on top of
|
||||
the OpenStack overcloud. This virtual machine is called the
|
||||
"conversion appliance". Note this virtual machine is unrelated to the
|
||||
guest which is being converted.
|
||||
|
||||
The reason for this is because to create Cinder volumes that will
|
||||
contain the guest data (for the converted guest) we must attach those
|
||||
Cinder volumes to an OpenStack virtual machine.
|
||||
|
||||
When virt-v2v is running in the conversion appliance, you must supply
|
||||
the name or UUID of the conversion appliance to virt-v2v, eg:
|
||||
|
||||
$ openstack server list
|
||||
+--------------------------------------+-----------+--------+
|
||||
| ID | Name | Status |
|
||||
+--------------------------------------+-----------+--------+
|
||||
| bbb0147a-44b9-4d19-9a9d-10ca9a984744 | test1 | ACTIVE |
|
||||
+--------------------------------------+-----------+--------+
|
||||
|
||||
# virt-v2v [...] \
|
||||
-o openstack -oo server-id=bbb0147a-44b9-4d19-9a9d-10ca9a984744
|
||||
|
||||
or:
|
||||
|
||||
# virt-v2v [...] -o openstack -oo server-id=test1
|
||||
|
||||
You can run many parallel conversions inside a single conversion
|
||||
appliance if you want, subject to having enough resources available.
|
||||
However OpenStack itself imposes a limit that you should be aware of:
|
||||
OpenStack cannot attach more than around 25 disks [the exact number
|
||||
varies with configuration] to a single appliance, and that limits the
|
||||
number of guests which can be converted in parallel, because each
|
||||
guest's disk must be attached to the appliance while being copied.
|
||||
|
||||
=head2 OPENSTACK: AUTHENTICATION
|
||||
|
||||
Converting to OpenStack requires access to the tenant (non-admin) API
|
||||
endpoints. You will need to either set up your C<$OS_*> environment
|
||||
variables or use output options on the virt-v2v command line to
|
||||
authenticate with OpenStack.
|
||||
|
||||
Normally there is a file called something like C<stackrc>,
|
||||
C<overcloudrc> etc which you can simply C<source> to set everything up.
|
||||
|
||||
For example:
|
||||
|
||||
export OS_USERNAME=admin
|
||||
|
||||
or:
|
||||
|
||||
virt-v2v [...] -o openstack -oo os-username=admin
|
||||
|
||||
are equivalent, and have the same effect as using I<--os-username> on
|
||||
the command line of OpenStack tools.
|
||||
|
||||
=head2 OPENSTACK: RUNNING AS ROOT
|
||||
|
||||
Because virt-v2v must access Cinder volumes which are presented as
|
||||
F</dev> devices to the conversion appliance, virt-v2v must usually run
|
||||
as root in I<-o openstack> mode.
|
||||
|
||||
If you use C<sudo> to start virt-v2v and you are using environment
|
||||
variables for authentication, remember to use the C<sudo -E> option to
|
||||
preserve the environment.
|
||||
|
||||
=head2 OPENSTACK: GUEST ID
|
||||
|
||||
virt-v2v [...] -o openstack -oo guest-id=123-456-7890
|
||||
|
||||
You may optionally specify I<-oo guest-id=...> on the command line.
|
||||
The ID (which can be any string) is saved on each Cinder volume in the
|
||||
C<virt_v2v_guest_id> volume property.
|
||||
|
||||
This can be used to find disks associated with a guest, or to
|
||||
associate which disks are related to which guests when converting many
|
||||
guests.
|
||||
|
||||
=head2 OPENSTACK: CONVERTING A GUEST
|
||||
|
||||
The final command to convert the guest, running as root, will be:
|
||||
|
||||
# virt-v2v [-i options ...] \
|
||||
-o openstack -oo server-id=NAME|UUID [-oo guest-id=ID]
|
||||
|
||||
If you include authentication options on the command line then:
|
||||
|
||||
# virt-v2v [-i options ...] \
|
||||
-o openstack -oo server-id=NAME|UUID -oo os-username=admin [etc]
|
||||
|
||||
=head2 OPENSTACK: BOOTING THE GUEST
|
||||
|
||||
Guests are converted as Cinder volume(s) (one volume per disk in the
|
||||
original guest). To boot them use the
|
||||
C<openstack server create --volume> option:
|
||||
|
||||
$ openstack volume list
|
||||
+--------------------------------------+---------------+-----------+
|
||||
| ID | Name | Status |
|
||||
+--------------------------------------+---------------+-----------+
|
||||
| c4d06d15-22ef-462e-9eff-ab54ab285a1f | fedora-27-sda | available |
|
||||
+--------------------------------------+---------------+-----------+
|
||||
$ openstack server create \
|
||||
--flavor x1.small \
|
||||
--volume c4d06d15-22ef-462e-9eff-ab54ab285a1f \
|
||||
myguest
|
||||
$ openstack console url show myguest
|
||||
|
||||
=head2 OPENSTACK: OTHER CONVERSION OPTIONS
|
||||
|
||||
To specify the Cinder volume type, use I<-os>. If not specified then
|
||||
no Cinder volume type is used.
|
||||
|
||||
The following options are B<not> supported with OpenStack: I<-oa>,
|
||||
I<-of>.
|
||||
|
||||
=head1 OUTPUT TO GLANCE
|
||||
|
||||
Note this is a legacy option. In most cases you should use
|
||||
L</OUTPUT TO OPENSTACK> instead.
|
||||
|
||||
To output to OpenStack Glance, use the I<-o glance> option.
|
||||
|
||||
This runs the L<glance(1)> CLI program which must be installed on the
|
||||
@@ -2184,34 +2338,7 @@ Glance disks either. If the guest has multiple disks, then the first
|
||||
(assumed to be the system disk) will have the name of the guest, and
|
||||
the second and subsequent data disks will be called
|
||||
C<I<guestname>-disk2>, C<I<guestname>-disk3> etc. It may be best to
|
||||
leave the system disk in Glance, and import the data disks to Cinder
|
||||
(see next section).
|
||||
|
||||
=head2 Importing disks into Cinder
|
||||
|
||||
Since most virt-v2v guests are "pets", Glance is perhaps not the best
|
||||
place to store them. There is no way for virt-v2v to upload directly
|
||||
to Cinder (L<https://bugzilla.redhat.com/1155229>). There are two
|
||||
ways to upload to Cinder:
|
||||
|
||||
=over 4
|
||||
|
||||
=item 1.
|
||||
|
||||
Import the image to Glance first (ie. I<-o glance>) and then copy it
|
||||
to Cinder:
|
||||
|
||||
cinder create --image-id <GLANCE-IMAGE-UUID> <SIZE>
|
||||
|
||||
=item 2.
|
||||
|
||||
Create (through some other means) a new volume / LUN in your Cinder
|
||||
backing store. Migrate the guest to this volume (using I<-o local>).
|
||||
Then ask Cinder to take over management of the volume using:
|
||||
|
||||
cinder manage <VOLUMEREF>
|
||||
|
||||
=back
|
||||
leave the system disk in Glance, and import the data disks to Cinder.
|
||||
|
||||
=head1 RESOURCE REQUIREMENTS
|
||||
|
||||
@@ -2414,6 +2541,11 @@ see L<http://libvirt.org/auth.html>. Alternatively, use
|
||||
I<-oc qemu:///session>, which will write to your per-user libvirt
|
||||
instance.
|
||||
|
||||
=item Writing to Openstack
|
||||
|
||||
Because of how Cinder volumes are presented as F</dev> block devices,
|
||||
using I<-o openstack> normally requires that virt-v2v is run as root.
|
||||
|
||||
=item Writing to Glance
|
||||
|
||||
This does I<not> need root (in fact it probably won’t work), but may
|
||||
|
||||
Reference in New Issue
Block a user