[Libguestfs] [PATCH v2 2/2] v2v: Add -o openstack target, writes to OpenStack & Cinder using APIs.

Richard W.M. Jones rjones at redhat.com
Thu Aug 30 09:47:57 UTC 2018


---
 v2v/Makefile.am             |   4 +
 v2v/cmdline.ml              |  26 +++
 v2v/output_openstack.ml     | 408 ++++++++++++++++++++++++++++++++++++
 v2v/output_openstack.mli    |  32 +++
 v2v/test-v2v-o-openstack.sh |  67 ++++++
 v2v/virt-v2v.pod            | 190 +++++++++++++----
 6 files changed, 689 insertions(+), 38 deletions(-)

diff --git a/v2v/Makefile.am b/v2v/Makefile.am
index 6e78ec4fb..d449307af 100644
--- a/v2v/Makefile.am
+++ b/v2v/Makefile.am
@@ -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 \
diff --git a/v2v/cmdline.ml b/v2v/cmdline.ml
index a5c125361..f051738b2 100644
--- a/v2v/cmdline.ml
+++ b/v2v/cmdline.ml
@@ -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";
diff --git a/v2v/output_openstack.ml b/v2v/output_openstack.ml
new file mode 100644
index 000000000..d5376c401
--- /dev/null
+++ b/v2v/output_openstack.ml
@@ -0,0 +1,408 @@
+(* 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 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;
+
+  (* 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 authentication = ref [] in
+  List.iter (
+    function
+    | "server-id", v ->
+       server_id := Some v
+    | "dev-disk-by-id", v ->
+       dev_disk_by_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 dev_disk_by_id = !dev_disk_by_id in
+  { server_id; authentication; dev_disk_by_id }
+
+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
+    let rec loop json =
+      match object_get_string "status" json with
+      | "creating" ->
+         (match run_openstack_command_capture_json args with
+          | None ->
+             error (f_"openstack: failed to query cinder volume status, see earlier error messages")
+          | Some json -> loop json
+         );
+      | "available" -> (* done *) ()
+      | status ->
+         error (f_"openstack: unknown volume status \"%s\": expected \"creating\" or \"available\"") status
+    in
+    loop json;
+    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 ?properties id =
+    let args = ref [ "volume"; "set" ] in
+
+    Option.may (List.push_back_list args) properties;
+    Option.may (
+      fun description ->
+        List.push_back_list args ["--description"; description]
+    ) description;
+    Option.may (
+      fun bootable ->
+        List.push_back args
+                       (if bootable then "--bootable" else "--non-bootable")
+    ) bootable;
+    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
+    let start_t = gettimeofday () in
+    let rec loop () =
+      if gettimeofday () -. start_t > float_of_int attach_timeout then
+        error (f_"openstack: timed out waiting for cinder volume %s to attach to the conversion appliance") id;
+
+      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 :: _ -> dev_disk_by_id // d
+      | [] ->
+         sleep 1;
+         loop ()
+    in
+    loop ()
+  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 (
+          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. *)
+    volume_ids <-
+      List.map (
+        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.
+           *)
+          create_cinder_volume name description ov.ov_virtual_size
+      ) 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);
+
+    (* Set the metadata on each Cinder volume. *)
+    let properties =
+      Openstack_image_properties.create source target_buses
+                                        guestcaps inspect target_firmware in
+    let properties =
+      List.flatten (
+        List.map (
+          fun (k, v) -> [ "--image-property"; sprintf "%s=%s" k v ]
+        ) properties
+      ) in
+
+    List.iteri (
+      fun i id ->
+        (* We set a "non-temporary" description here - see above. *)
+        let description =
+          sprintf "%s disk %d/%d converted by virt-v2v"
+                  source.s_name (i+1) nr_disks in
+
+        (* XXX See RHBZ#1308535 for why this is wrong. *)
+        let bootable = if i = 0 then Some true else None in
+
+        update_cinder_volume_metadata ?bootable ~description ~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"
diff --git a/v2v/output_openstack.mli b/v2v/output_openstack.mli
new file mode 100644
index 000000000..2c1aa8712
--- /dev/null
+++ b/v2v/output_openstack.mli
@@ -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. *)
diff --git a/v2v/test-v2v-o-openstack.sh b/v2v/test-v2v-o-openstack.sh
new file mode 100755
index 000000000..7f19730a5
--- /dev/null
+++ b/v2v/test-v2v-o-openstack.sh
@@ -0,0 +1,67 @@
+#!/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 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 'server remove volume' $d/log
+
+rm -r $d
diff --git a/v2v/virt-v2v.pod b/v2v/virt-v2v.pod
index 3de200d25..d09309dd8 100644
--- a/v2v/virt-v2v.pod
+++ b/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,12 @@ To display short help on what options are available you can use:
 
  virt-v2v -o rhv-upload -oo "?"
 
+=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 +693,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 +800,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:
 
@@ -2138,8 +2161,121 @@ 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: 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
+
+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
@@ -2170,34 +2306,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
 
@@ -2400,6 +2509,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
-- 
2.18.0




More information about the Libguestfs mailing list