1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203
|
(* virt-v2v
* Copyright (C) 2009-2019 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 Std_utils
open Tools_utils
open Common_gettext.Gettext
open Types
open Utils
open Xpath_helpers
open Create_libvirt_xml
let arch_is_sane_or_die =
let rex = PCRE.compile ~caseless:true "^[-_a-z0-9]+$" in
fun arch -> assert (PCRE.matches rex arch)
let target_features_of_capabilities_doc doc arch =
let xpathctx = Xml.xpath_new_context doc in
let expr =
(* Check the arch is sane. It comes from untrusted input. This
* avoids XPath injection below.
*)
arch_is_sane_or_die arch;
(* NB: Pay attention to the square brackets. This returns the
* <guest> nodes!
*)
sprintf "/capabilities/guest[arch[@name='%s']/domain/@type='kvm']" arch in
let obj = Xml.xpath_eval_expression xpathctx expr in
if Xml.xpathobj_nr_nodes obj < 1 then (
(* Old virt-v2v used to die here, but that seems unfair since the
* user has gone through conversion before we reach here.
*)
warning (f_"the target hypervisor does not support a %s KVM guest") arch;
[]
) else (
let node (* first matching <guest> *) = Xml.xpathobj_node obj 0 in
Xml.xpathctx_set_current_context xpathctx node;
(* Get guest/features/* nodes. *)
let features = xpath_get_nodes xpathctx "features/*" in
List.map Xml.node_name features
)
class output_libvirt oc output_pool = object
inherit output
val mutable capabilities_doc = None
val mutable pool_name = None
method as_options =
match oc with
| None -> sprintf "-o libvirt -os %s" output_pool
| Some uri -> sprintf "-o libvirt -oc %s -os %s" uri output_pool
method prepare_targets source overlays _ _ _ _ =
(* Get the capabilities from libvirt. *)
let xml = Libvirt_utils.capabilities ?conn:oc () in
debug "libvirt capabilities XML:\n%s" xml;
(* This just checks that the capabilities XML is well-formed,
* early so that we catch parsing errors before conversion.
*)
let doc = Xml.parse_memory xml in
(* Stash the capabilities XML, since we cannot get the bits we
* need from it until we know the guest architecture, which happens
* after conversion.
*)
capabilities_doc <- Some doc;
(* Does the domain already exist on the target? (RHBZ#889082) *)
if Libvirt_utils.domain_exists ?conn:oc source.s_name then (
if source.s_hypervisor = Physical then (* virt-p2v user *)
error (f_"a libvirt domain called ‘%s’ already exists on the target.\n\nIf using virt-p2v, select a different ‘Name’ in the ‘Target properties’. Or delete the existing domain on the target using the ‘virsh undefine’ command.")
source.s_name
else (* !virt-p2v *)
error (f_"a libvirt domain called ‘%s’ already exists on the target.\n\nIf using virt-v2v directly, use the ‘-on’ option to select a different name. Or delete the existing domain on the target using the ‘virsh undefine’ command.")
source.s_name
);
(* Connect to output libvirt instance and check that the pool exists
* and dump out its XML.
*)
let xml = Libvirt_utils.pool_dumpxml ?conn:oc output_pool in
let doc = Xml.parse_memory xml in
let xpathctx = Xml.xpath_new_context doc in
let xpath_string = xpath_string xpathctx in
(* We can only output to a pool of type 'dir' (directory). *)
if xpath_string "/pool/@type" <> Some "dir" then
error (f_"-o libvirt: output pool ‘%s’ is not a directory (type='dir'). See virt-v2v-output-local(1)") output_pool;
let target_path =
match xpath_string "/pool/target/path/text()" with
| None ->
error (f_"-o libvirt: output pool ‘%s’ does not have /pool/target/path element. See virt-v2v-output-local(1)") output_pool
| Some dir when not (is_directory dir) ->
error (f_"-o libvirt: output pool ‘%s’ has type='dir' but the /pool/target/path element is not a local directory. See virt-v2v-output-local(1)") output_pool
| Some dir -> dir in
(* Get the name of the pool, since we have to use that
* (and not the UUID) in the XML of the guest.
*)
let name =
match xpath_string "/pool/name/text()" with
| None ->
error (f_"-o libvirt: output pool ‘%s’ does not have /pool/name element. See virt-v2v-output-local(1)") output_pool
| Some name -> name in
pool_name <- Some name;
(* Set up the targets. *)
List.map (
fun (_, ov) ->
TargetFile (target_path // source.s_name ^ "-" ^ ov.ov_sd)
) overlays
method supported_firmware = [ TargetBIOS; TargetUEFI ]
method check_target_firmware guestcaps target_firmware =
match target_firmware with
| TargetBIOS -> ()
| TargetUEFI ->
(* XXX Can remove this method when libvirt supports
* <loader type="efi"/> since then it will be up to
* libvirt to check this.
*)
error_unless_uefi_firmware guestcaps.gcaps_arch
method create_metadata source targets
target_buses guestcaps inspect target_firmware =
(* We copied directly into the final pool directory. However we
* have to tell libvirt.
*)
let cmd = [ "virsh" ] @
(if quiet () then [ "-q" ] else []) @
(match oc with
| None -> []
| Some uri -> [ "-c"; uri; ]) @
[ "pool-refresh"; output_pool ] in
if run_command cmd <> 0 then
warning (f_"could not refresh libvirt pool %s") output_pool;
let pool_name =
match pool_name with
| None -> output_pool
| Some n -> n in
(* Parse the capabilities XML in order to get the supported features. *)
let doc =
match capabilities_doc with
| None -> assert false
| Some doc -> doc in
let target_features =
target_features_of_capabilities_doc doc guestcaps.gcaps_arch in
(* Create the metadata. *)
let doc =
create_libvirt_xml ~pool:pool_name source targets target_buses
guestcaps target_features target_firmware inspect in
let tmpfile, chan = Filename.open_temp_file "v2vlibvirt" ".xml" in
DOM.doc_to_chan chan doc;
close_out chan;
if verbose () then (
eprintf "resulting XML for libvirt:\n%!";
DOM.doc_to_chan stderr doc;
eprintf "\n%!";
);
(* Define the domain in libvirt. *)
let cmd = [ "virsh" ] @
(if quiet () then [ "-q" ] else []) @
(match oc with
| None -> []
| Some uri -> [ "-c"; uri; ]) @
[ "define"; tmpfile ] in
if run_command cmd = 0 then (
try Unix.unlink tmpfile with _ -> ()
) else (
warning (f_"could not define libvirt domain. The libvirt XML is still available in ‘%s’. Try running ‘virsh define %s’ yourself instead.")
tmpfile tmpfile
);
end
let output_libvirt = new output_libvirt
let () = Modules_list.register_output_module "libvirt"
|