Currently virt-v2v has few custom C-based functions for libvirt operations, which are limited in what they do, and there is a lot of duplicated code. Instead, switch to ocaml-libvirt for all the libvirt interaction currently done by the Libvirt_utils module. This has few advantages: - each input & output module now opens a libvirt connection only once, only when needed - no need to pass URIs and passwords around, if not needed - a wider range of libvirt APIs can now be used, with no need to create bindings manually The hierarchy of input_libvirt* classes is changed to take a Lazy object with the libvirt connection, accessing it through a "proctected" method: this way, the connection is opened only at the first access. Also, the Libvirt_utils module now is just helpers around the Libvirt module, to centralize error handling, and few common operations. --- docs/C_SOURCE_FILES | 1 - po/POTFILES | 1 - v2v/Makefile.am | 6 +- v2v/copy_to_local.ml | 7 +- v2v/input_libvirt.ml | 20 +- v2v/input_libvirt_other.ml | 27 +- v2v/input_libvirt_other.mli | 5 +- v2v/input_libvirt_vcenter_https.ml | 13 +- v2v/input_libvirt_vcenter_https.mli | 2 +- v2v/input_libvirt_vddk.ml | 15 +- v2v/input_libvirt_vddk.mli | 4 +- v2v/input_libvirt_xen_ssh.ml | 13 +- v2v/input_libvirt_xen_ssh.mli | 2 +- v2v/libvirt_utils-c.c | 517 ---------------------------- v2v/libvirt_utils.ml | 95 ++++- v2v/libvirt_utils.mli | 51 ++- v2v/output_libvirt.ml | 17 +- v2v/parse_libvirt_xml.ml | 23 +- v2v/parse_libvirt_xml.mli | 11 +- 19 files changed, 207 insertions(+), 623 deletions(-) delete mode 100644 v2v/libvirt_utils-c.c diff --git a/docs/C_SOURCE_FILES b/docs/C_SOURCE_FILES index 7f1c60b30..519acedd5 100644 --- a/docs/C_SOURCE_FILES +++ b/docs/C_SOURCE_FILES @@ -414,6 +414,5 @@ utils/boot-analysis/boot-analysis.h utils/boot-benchmark/boot-benchmark.c utils/qemu-boot/qemu-boot.c utils/qemu-speed-test/qemu-speed-test.c -v2v/libvirt_utils-c.c v2v/qemuopts-c.c v2v/test-harness/dummy.c diff --git a/po/POTFILES b/po/POTFILES index 79f4b8c56..8d603162f 100644 --- a/po/POTFILES +++ b/po/POTFILES @@ -471,6 +471,5 @@ utils/boot-benchmark/boot-benchmark.c utils/max-disks/max-disks.pl utils/qemu-boot/qemu-boot.c utils/qemu-speed-test/qemu-speed-test.c -v2v/libvirt_utils-c.c v2v/qemuopts-c.c v2v/test-harness/dummy.c diff --git a/v2v/Makefile.am b/v2v/Makefile.am index 5999a6b50..13948c24e 100644 --- a/v2v/Makefile.am +++ b/v2v/Makefile.am @@ -163,7 +163,6 @@ SOURCES_ML = \ v2v.ml SOURCES_C = \ - libvirt_utils-c.c \ qemuopts-c.c # These files are generated and contain *.py embedded as an OCaml string. @@ -212,6 +211,7 @@ OCAMLPACKAGES = \ -I $(top_builddir)/common/mlpcre \ -I $(top_builddir)/common/mlxml \ -I $(top_builddir)/common/mltools \ + -I $(top_builddir)/common/mllibvirt \ -I $(top_builddir)/customize if HAVE_OCAML_PKG_GETTEXT OCAMLPACKAGES += -package gettext-stub @@ -242,6 +242,7 @@ OCAMLLINKFLAGS = \ mlxml.$(MLARCHIVE) \ mlcutils.$(MLARCHIVE) \ mltools.$(MLARCHIVE) \ + mllibvirt.$(MLARCHIVE) \ $(LINK_CUSTOM_OCAMLC_ONLY) virt_v2v_DEPENDENCIES = $(OBJECTS) $(top_srcdir)/ocaml-link.sh @@ -251,7 +252,7 @@ virt_v2v_LINK = \ $(OBJECTS) -o $@ virt_v2v_copy_to_local_SOURCES = \ - libvirt_utils-c.c + dummy.c virt_v2v_copy_to_local_CPPFLAGS = \ -I. \ -I$(top_builddir) \ @@ -284,6 +285,7 @@ virt_v2v_copy_to_local_DEPENDENCIES = \ ../common/mlpcre/mlpcre.$(MLARCHIVE) \ ../common/mlutils/mlcutils.$(MLARCHIVE) \ ../common/mltools/mltools.$(MLARCHIVE) \ + ../common/mllibvirt/mllibvirt.$(MLARCHIVE) \ $(top_srcdir)/ocaml-link.sh virt_v2v_copy_to_local_LINK = \ $(top_srcdir)/ocaml-link.sh -cclib '$(OCAMLCLIBS)' -- \ diff --git a/v2v/copy_to_local.ml b/v2v/copy_to_local.ml index 408cbdebc..e841b44c5 100644 --- a/v2v/copy_to_local.ml +++ b/v2v/copy_to_local.ml @@ -125,7 +125,12 @@ read the man page virt-v2v-copy-to-local(1). (* Get the remote libvirt XML. *) message (f_"Fetching the remote libvirt XML metadata ..."); - let xml = Libvirt_utils.dumpxml ?password_file ~conn:input_conn guest_name in + let xml = + let auth = Libvirt_utils.auth_for_password_file ?password_file () in + let conn = Libvirt.Connect.connect_auth ~name:input_conn auth in + let dom = Libvirt_utils.get_domain conn guest_name in + (* Use XmlSecure to get passwords (RHBZ#1174123). *) + Libvirt.Domain.get_xml_desc_flags dom [Libvirt.Domain.XmlSecure] in debug "libvirt XML from remote server:\n%s" xml; diff --git a/v2v/input_libvirt.ml b/v2v/input_libvirt.ml index 0e4afa157..3418e9f28 100644 --- a/v2v/input_libvirt.ml +++ b/v2v/input_libvirt.ml @@ -28,9 +28,17 @@ open Utils (* Choose the right subclass based on the URI. *) let input_libvirt input_conn input_password input_transport guest = + (* Create a lazy object to open the connection to libvirt only when + * needed. + *) + let lazy_conn = + lazy ( + let auth = Libvirt_utils.auth_for_password_file ?password_file:input_password () in + Libvirt.Connect.connect_auth ?name:input_conn auth + ) in match input_conn with | None -> - Input_libvirt_other.input_libvirt_other input_conn input_password guest + Input_libvirt_other.input_libvirt_other lazy_conn guest | Some orig_uri -> let { Xml.uri_server = server; uri_scheme = scheme } as parsed_uri = @@ -45,22 +53,22 @@ let input_libvirt input_conn input_password input_transport guest = | Some _, None, _ (* No scheme? *) | Some _, Some "", _ -> - Input_libvirt_other.input_libvirt_other input_conn input_password guest + Input_libvirt_other.input_libvirt_other lazy_conn guest (* vCenter over https. *) | Some server, Some ("esx"|"gsx"|"vpx"), None -> Input_libvirt_vcenter_https.input_libvirt_vcenter_https - input_conn input_password parsed_uri server guest + lazy_conn input_password parsed_uri server guest (* vCenter or ESXi using nbdkit vddk plugin *) | Some server, Some ("esx"|"gsx"|"vpx"), Some (`VDDK vddk_options) -> - Input_libvirt_vddk.input_libvirt_vddk input_conn input_password + Input_libvirt_vddk.input_libvirt_vddk lazy_conn input_conn input_password vddk_options parsed_uri guest (* Xen over SSH *) | Some server, Some "xen+ssh", _ -> Input_libvirt_xen_ssh.input_libvirt_xen_ssh - input_conn input_password parsed_uri server guest + lazy_conn parsed_uri server guest (* Old virt-v2v also supported qemu+ssh://. However I am * deliberately not supporting this in new virt-v2v. Don't @@ -71,6 +79,6 @@ let input_libvirt input_conn input_password input_transport guest = | Some _, Some _, _ -> warning (f_"no support for remote libvirt connections to '-ic %s'. The conversion may fail when it tries to read the source disks.") orig_uri; - Input_libvirt_other.input_libvirt_other input_conn input_password guest + Input_libvirt_other.input_libvirt_other lazy_conn guest let () = Modules_list.register_input_module "libvirt" diff --git a/v2v/input_libvirt_other.ml b/v2v/input_libvirt_other.ml index dc69d64e5..aff1a872b 100644 --- a/v2v/input_libvirt_other.ml +++ b/v2v/input_libvirt_other.ml @@ -40,35 +40,28 @@ let error_if_libvirt_does_not_support_json_backingfile () = error (f_"because of libvirt bug https://bugzilla.redhat.com/1134878 you must EITHER upgrade to libvirt >= 2.1.0 OR set this environment variable:\n\nexport LIBGUESTFS_BACKEND=direct\n\nand then rerun the virt-v2v command.") (* Superclass. *) -class virtual input_libvirt input_conn (input_password : string option) guest = -object +class virtual input_libvirt lazy_conn guest = +object (self) inherit input method as_options = - sprintf "-i libvirt%s %s" - (match input_conn with - | None -> "" - | Some uri -> " -ic " ^ uri) - guest + sprintf "-i libvirt -ic %s %s" (Libvirt.Connect.get_uri self#conn) guest + + method private conn : Libvirt.rw Libvirt.Connect.t = + Lazy.force lazy_conn end (* Subclass specialized for handling anything that's *not* VMware vCenter * or Xen. *) -class input_libvirt_other input_conn input_password guest = -object - inherit input_libvirt input_conn input_password guest +class input_libvirt_other lazy_conn guest = +object (self) + inherit input_libvirt lazy_conn guest method source () = debug "input_libvirt_other: source ()"; - (* Get the libvirt XML. This also checks (as a side-effect) - * that the domain is not running. (RHBZ#1138586) - *) - let xml = Libvirt_utils.dumpxml ?password_file:input_password - ?conn:input_conn guest in - - let source, disks = parse_libvirt_xml ?conn:input_conn xml in + let source, disks, _ = parse_libvirt_domain self#conn guest in let disks = List.map (fun { p_source_disk = disk } -> disk) disks in { source with s_disks = disks } end diff --git a/v2v/input_libvirt_other.mli b/v2v/input_libvirt_other.mli index e6671a76b..10f574281 100644 --- a/v2v/input_libvirt_other.mli +++ b/v2v/input_libvirt_other.mli @@ -20,11 +20,12 @@ val error_if_libvirt_does_not_support_json_backingfile : unit -> unit -class virtual input_libvirt : string option -> string option -> string -> object +class virtual input_libvirt : Libvirt.rw Libvirt.Connect.t Lazy.t -> string -> object method precheck : unit -> unit method as_options : string method virtual source : unit -> Types.source method adjust_overlay_parameters : Types.overlay -> unit + method private conn : Libvirt.rw Libvirt.Connect.t end -val input_libvirt_other : string option -> string option -> string -> Types.input +val input_libvirt_other : Libvirt.rw Libvirt.Connect.t Lazy.t -> string -> Types.input diff --git a/v2v/input_libvirt_vcenter_https.ml b/v2v/input_libvirt_vcenter_https.ml index 96f009771..adc05f8c1 100644 --- a/v2v/input_libvirt_vcenter_https.ml +++ b/v2v/input_libvirt_vcenter_https.ml @@ -36,9 +36,9 @@ let readahead_for_copying = Some (64 * 1024 * 1024) (* Subclass specialized for handling VMware vCenter over https. *) class input_libvirt_vcenter_https - input_conn input_password parsed_uri server guest = -object - inherit input_libvirt input_conn input_password guest + lazy_conn input_password parsed_uri server guest = +object (self) + inherit input_libvirt lazy_conn guest val saved_source_paths = Hashtbl.create 13 val mutable dcPath = "" @@ -61,12 +61,7 @@ object unsetenv "ALL_PROXY"; unsetenv "NO_PROXY"; - (* Get the libvirt XML. This also checks (as a side-effect) - * that the domain is not running. (RHBZ#1138586) - *) - let xml = Libvirt_utils.dumpxml ?password_file:input_password - ?conn:input_conn guest in - let source, disks = parse_libvirt_xml ?conn:input_conn xml in + let source, disks, xml = parse_libvirt_domain self#conn guest in (* Find the <vmware:datacenterpath> element from the XML. This * was added in libvirt >= 1.2.20. diff --git a/v2v/input_libvirt_vcenter_https.mli b/v2v/input_libvirt_vcenter_https.mli index 966cc674c..c59a34a71 100644 --- a/v2v/input_libvirt_vcenter_https.mli +++ b/v2v/input_libvirt_vcenter_https.mli @@ -18,4 +18,4 @@ (** [-i libvirt] when the source is VMware vCenter *) -val input_libvirt_vcenter_https : string option -> string option -> Xml.uri -> string -> string -> Types.input +val input_libvirt_vcenter_https : Libvirt.rw Libvirt.Connect.t Lazy.t -> string option -> Xml.uri -> string -> string -> Types.input diff --git a/v2v/input_libvirt_vddk.ml b/v2v/input_libvirt_vddk.ml index 97c7cb532..827c4dbf4 100644 --- a/v2v/input_libvirt_vddk.ml +++ b/v2v/input_libvirt_vddk.ml @@ -93,8 +93,8 @@ let parse_input_options options = options (* Subclass specialized for handling VMware via nbdkit vddk plugin. *) -class input_libvirt_vddk input_conn input_password vddk_options parsed_uri - guest = +class input_libvirt_vddk lazy_conn input_conn input_password vddk_options + parsed_uri guest = (* The VDDK path. *) let libdir = try Some (List.assoc "libdir" vddk_options) @@ -198,8 +198,8 @@ See also the virt-v2v-input-vmware(1) manual.") libNN error (f_"nbdkit was compiled without SELinux support. You will have to recompile nbdkit with libselinux-devel installed, or else set SELinux to Permissive mode while doing the conversion.") in -object - inherit input_libvirt input_conn input_password guest as super +object (self) + inherit input_libvirt lazy_conn guest as super method precheck () = error_unless_vddk_libdir (); @@ -219,12 +219,7 @@ object pt_options method source () = - (* Get the libvirt XML. This also checks (as a side-effect) - * that the domain is not running. (RHBZ#1138586) - *) - let xml = Libvirt_utils.dumpxml ?password_file:input_password - ?conn:input_conn guest in - let source, disks = parse_libvirt_xml ?conn:input_conn xml in + let source, disks, xml = parse_libvirt_domain self#conn guest in (* Find the <vmware:moref> element from the XML. This was added * in libvirt >= 3.7 and is required. diff --git a/v2v/input_libvirt_vddk.mli b/v2v/input_libvirt_vddk.mli index d321677d8..66d5f6f15 100644 --- a/v2v/input_libvirt_vddk.mli +++ b/v2v/input_libvirt_vddk.mli @@ -25,7 +25,7 @@ val print_input_options : unit -> unit val parse_input_options : (string * string) list -> vddk_options (** Print and parse vddk -io options. *) -val input_libvirt_vddk : string option -> string option -> vddk_options -> Xml.uri -> string -> Types.input -(** [input_libvirt_vddk input_conn input_password vddk_options parsed_uri guest] +val input_libvirt_vddk : Libvirt.rw Libvirt.Connect.t Lazy.t -> string option -> string option -> vddk_options -> Xml.uri -> string -> Types.input +(** [input_libvirt_vddk lazy_conn vddk_options parsed_uri guest] creates and returns a {!Types.input} object specialized for reading the guest disks using the nbdkit vddk plugin. *) diff --git a/v2v/input_libvirt_xen_ssh.ml b/v2v/input_libvirt_xen_ssh.ml index 6b77db029..f1d7d3bed 100644 --- a/v2v/input_libvirt_xen_ssh.ml +++ b/v2v/input_libvirt_xen_ssh.ml @@ -30,9 +30,9 @@ open Input_libvirt_other open Printf (* Subclass specialized for handling Xen over SSH. *) -class input_libvirt_xen_ssh input_conn input_password parsed_uri server guest = -object - inherit input_libvirt input_conn input_password guest +class input_libvirt_xen_ssh lazy_conn parsed_uri server guest = +object (self) + inherit input_libvirt lazy_conn guest method precheck () = if backend_is_libvirt () then @@ -43,12 +43,7 @@ object method source () = debug "input_libvirt_xen_ssh: source: server %s" server; - (* Get the libvirt XML. This also checks (as a side-effect) - * that the domain is not running. (RHBZ#1138586) - *) - let xml = Libvirt_utils.dumpxml ?password_file:input_password - ?conn:input_conn guest in - let source, disks = parse_libvirt_xml ?conn:input_conn xml in + let source, disks, _ = parse_libvirt_domain self#conn guest in (* Map the <source/> filename (which is relative to the remote * Xen server) to an ssh URI. This is a JSON URI looking something diff --git a/v2v/input_libvirt_xen_ssh.mli b/v2v/input_libvirt_xen_ssh.mli index 73c2645a1..6649b9883 100644 --- a/v2v/input_libvirt_xen_ssh.mli +++ b/v2v/input_libvirt_xen_ssh.mli @@ -18,4 +18,4 @@ (** [-i libvirt] when the source is Xen *) -val input_libvirt_xen_ssh : string option -> string option -> Xml.uri -> string -> string -> Types.input +val input_libvirt_xen_ssh : Libvirt.rw Libvirt.Connect.t Lazy.t -> Xml.uri -> string -> string -> Types.input diff --git a/v2v/libvirt_utils-c.c b/v2v/libvirt_utils-c.c deleted file mode 100644 index 5ca0d5363..000000000 --- a/v2v/libvirt_utils-c.c +++ /dev/null @@ -1,517 +0,0 @@ -/* 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. - */ - -/** - * This module implements various C<virsh>-like commands, but with - * non-broken authentication handling. - */ - -#include <config.h> - -#include <stdio.h> -#include <stdlib.h> -#include <stdarg.h> -#include <string.h> -#include <errno.h> -#include <libintl.h> - -#include <caml/alloc.h> -#include <caml/fail.h> -#include <caml/memory.h> -#include <caml/mlvalues.h> - -#include <libvirt/libvirt.h> -#include <libvirt/virterror.h> - -#include "guestfs.h" -#include "guestfs-utils.h" - -#pragma GCC diagnostic ignored "-Wmissing-prototypes" - -#define ERROR_MESSAGE_LEN 512 - -static void -ignore_errors (void *ignore, virErrorPtr ignore2) -{ - /* empty */ -} - -/* Get the remote domain state (running, etc.). Use virDomainGetState - * which is most efficient, but if it's not implemented, fall back to - * virDomainGetInfo. See equivalent code in virsh. - */ -static int -get_dom_state (virDomainPtr dom) -{ - int state, reason; - virErrorPtr err; - virDomainInfo info; - - if (virDomainGetState (dom, &state, &reason, 0) == 0) - return state; - - err = virGetLastError (); - if (!err || err->code != VIR_ERR_NO_SUPPORT) - return -1; - - if (virDomainGetInfo (dom, &info) == 0) - return info.state; - - return -1; -} - -/* See lib/libvirt-auth.c for why we need this. */ -static int -libvirt_auth_default_wrapper (virConnectCredentialPtr cred, - unsigned int ncred, - void *passwordvp) -{ - const char *password = passwordvp; - unsigned int i; - - if (password) { - /* If --password-file was specified on the command line, and the - * libvirt handler is asking for a password, return that. - */ - for (i = 0; i < ncred; ++i) { - if (cred[i].type == VIR_CRED_PASSPHRASE) { - cred[i].result = strdup (password); - cred[i].resultlen = strlen (password); - } - else { - cred[i].result = NULL; - cred[i].resultlen = 0; - } - } - return 0; - } - else { - /* No --password-file so call the default handler. */ - return virConnectAuthPtrDefault->cb (cred, ncred, - virConnectAuthPtrDefault->cbdata); - } -} - -virStoragePoolPtr -connect_and_load_pool (value connv, value poolnamev) -{ - CAMLparam2 (connv, poolnamev); - const char *conn_uri = NULL; - const char *poolname; - /* We have to assemble the error on the stack because a dynamic - * string couldn't be freed. - */ - char errmsg[ERROR_MESSAGE_LEN]; - virErrorPtr err; - virConnectPtr conn; - virStoragePoolPtr pool; - - if (connv != Val_int (0)) - conn_uri = String_val (Field (connv, 0)); /* Some conn */ - - /* We have to call the default authentication handler, not least - * since it handles all the PolicyKit crap. However it also makes - * coding this simpler. - */ - conn = virConnectOpenAuth (conn_uri, virConnectAuthPtrDefault, - VIR_CONNECT_RO); - if (conn == NULL) { - if (conn_uri) - snprintf (errmsg, sizeof errmsg, - _("cannot open libvirt connection ‘%s’"), conn_uri); - else - snprintf (errmsg, sizeof errmsg, _("cannot open libvirt connection")); - caml_invalid_argument (errmsg); - } - - /* Suppress default behaviour of printing errors to stderr. Note - * you can't set this to NULL to ignore errors; setting it to NULL - * restores the default error handler ... - */ - virConnSetErrorFunc (conn, NULL, ignore_errors); - - /* Look up the pool. */ - poolname = String_val (poolnamev); - - pool = virStoragePoolLookupByUUIDString (conn, poolname); - - if (!pool) - pool = virStoragePoolLookupByName (conn, poolname); - - if (!pool) { - err = virGetLastError (); - snprintf (errmsg, sizeof errmsg, - _("cannot find libvirt pool ‘%s’: %s\n\nUse ‘virsh pool-list --all’ to list all available pools, and ‘virsh pool-dumpxml <pool>’ to display details about a particular pool.\n\nTo set the pool which virt-v2v uses, add the ‘-os <pool>’ option."), - poolname, err->message); - virConnectClose (conn); - caml_invalid_argument (errmsg); - } - - CAMLreturnT (virStoragePoolPtr, pool); -} - -value -v2v_dumpxml (value passwordv, value connv, value domnamev) -{ - CAMLparam3 (passwordv, connv, domnamev); - CAMLlocal1 (retv); - const char *password = NULL; - const char *conn_uri = NULL; - const char *domname; - virConnectAuth authdata; - /* We have to assemble the error on the stack because a dynamic - * string couldn't be freed. - */ - char errmsg[ERROR_MESSAGE_LEN]; - virErrorPtr err; - virConnectPtr conn; - virDomainPtr dom; - int is_test_uri = 0; - char *xml; - - if (passwordv != Val_int (0)) - password = String_val (Field (passwordv, 0)); /* Some password */ - - if (connv != Val_int (0)) { - conn_uri = String_val (Field (connv, 0)); /* Some conn */ - is_test_uri = STRPREFIX (conn_uri, "test:"); - } - - /* Set up authentication wrapper. */ - authdata = *virConnectAuthPtrDefault; - authdata.cb = libvirt_auth_default_wrapper; - authdata.cbdata = (void *) password; - - /* Note this cannot be a read-only connection since we need to use - * the VIR_DOMAIN_XML_SECURE flag below. - */ - conn = virConnectOpenAuth (conn_uri, &authdata, 0); - if (conn == NULL) { - if (conn_uri) - snprintf (errmsg, sizeof errmsg, - _("cannot open libvirt connection ‘%s’"), conn_uri); - else - snprintf (errmsg, sizeof errmsg, _("cannot open libvirt connection")); - caml_invalid_argument (errmsg); - } - - /* Suppress default behaviour of printing errors to stderr. Note - * you can't set this to NULL to ignore errors; setting it to NULL - * restores the default error handler ... - */ - virConnSetErrorFunc (conn, NULL, ignore_errors); - - /* Look up the domain. */ - domname = String_val (domnamev); - - dom = virDomainLookupByUUIDString (conn, domname); - - if (!dom) - dom = virDomainLookupByName (conn, domname); - - if (!dom) { - err = virGetLastError (); - snprintf (errmsg, sizeof errmsg, - _("cannot find libvirt domain ‘%s’: %s"), domname, err->message); - virConnectClose (conn); - caml_invalid_argument (errmsg); - } - - /* As a side-effect we check that the domain is shut down. Of course - * this is only appropriate for virt-v2v. (RHBZ#1138586) - */ - if (!is_test_uri) { - const int state = get_dom_state (dom); - - if (state == VIR_DOMAIN_RUNNING || - state == VIR_DOMAIN_BLOCKED || - state == VIR_DOMAIN_PAUSED) { - snprintf (errmsg, sizeof errmsg, - _("libvirt domain ‘%s’ is running or paused. It must be shut down in order to perform virt-v2v conversion"), - domname); - virDomainFree (dom); - virConnectClose (conn); - caml_invalid_argument (errmsg); - } - } - - /* Use VIR_DOMAIN_XML_SECURE to get passwords (RHBZ#1174123). */ - xml = virDomainGetXMLDesc (dom, VIR_DOMAIN_XML_SECURE); - if (xml == NULL) { - err = virGetLastError (); - snprintf (errmsg, sizeof errmsg, - _("cannot fetch XML description of guest ‘%s’: %s"), - domname, err->message); - virDomainFree (dom); - virConnectClose (conn); - caml_invalid_argument (errmsg); - } - virDomainFree (dom); - virConnectClose (conn); - - retv = caml_copy_string (xml); - free (xml); - - CAMLreturn (retv); -} - -value -v2v_pool_dumpxml (value connv, value poolnamev) -{ - CAMLparam2 (connv, poolnamev); - CAMLlocal1 (retv); - /* We have to assemble the error on the stack because a dynamic - * string couldn't be freed. - */ - char errmsg[ERROR_MESSAGE_LEN]; - virErrorPtr err; - virConnectPtr conn; - virStoragePoolPtr pool; - char *xml; - - /* Look up the pool. */ - pool = connect_and_load_pool (connv, poolnamev); - conn = virStoragePoolGetConnect (pool); - - xml = virStoragePoolGetXMLDesc (pool, 0); - if (xml == NULL) { - err = virGetLastError (); - snprintf (errmsg, sizeof errmsg, - _("cannot fetch XML description of pool ‘%s’: %s"), - String_val (poolnamev), err->message); - virStoragePoolFree (pool); - virConnectClose (conn); - caml_invalid_argument (errmsg); - } - virStoragePoolFree (pool); - virConnectClose (conn); - - retv = caml_copy_string (xml); - free (xml); - - CAMLreturn (retv); -} - -value -v2v_vol_dumpxml (value connv, value poolnamev, value volnamev) -{ - CAMLparam3 (connv, poolnamev, volnamev); - CAMLlocal1 (retv); - const char *volname; - /* We have to assemble the error on the stack because a dynamic - * string couldn't be freed. - */ - char errmsg[ERROR_MESSAGE_LEN]; - virErrorPtr err; - virConnectPtr conn; - virStoragePoolPtr pool; - virStorageVolPtr vol; - char *xml; - - /* Look up the pool. */ - pool = connect_and_load_pool (connv, poolnamev); - conn = virStoragePoolGetConnect (pool); - - /* Look up the volume. */ - volname = String_val (volnamev); - - vol = virStorageVolLookupByName (pool, volname); - - if (!vol) { - err = virGetLastError (); - snprintf (errmsg, sizeof errmsg, - _("cannot find libvirt volume ‘%s’: %s"), volname, err->message); - virStoragePoolFree (pool); - virConnectClose (conn); - caml_invalid_argument (errmsg); - } - - xml = virStorageVolGetXMLDesc (vol, 0); - if (xml == NULL) { - err = virGetLastError (); - snprintf (errmsg, sizeof errmsg, - _("cannot fetch XML description of volume ‘%s’: %s"), - volname, err->message); - virStorageVolFree (vol); - virStoragePoolFree (pool); - virConnectClose (conn); - caml_invalid_argument (errmsg); - } - virStorageVolFree (vol); - virStoragePoolFree (pool); - virConnectClose (conn); - - retv = caml_copy_string (xml); - free (xml); - - CAMLreturn (retv); -} - -value -v2v_capabilities (value connv, value unitv) -{ - CAMLparam2 (connv, unitv); - CAMLlocal1 (capabilitiesv); - const char *conn_uri = NULL; - char *capabilities; - /* We have to assemble the error on the stack because a dynamic - * string couldn't be freed. - */ - char errmsg[ERROR_MESSAGE_LEN]; - virErrorPtr err; - virConnectPtr conn; - - if (connv != Val_int (0)) - conn_uri = String_val (Field (connv, 0)); /* Some conn */ - - /* We have to call the default authentication handler, not least - * since it handles all the PolicyKit crap. However it also makes - * coding this simpler. - */ - conn = virConnectOpenAuth (conn_uri, virConnectAuthPtrDefault, - VIR_CONNECT_RO); - if (conn == NULL) { - if (conn_uri) - snprintf (errmsg, sizeof errmsg, - _("cannot open libvirt connection ‘%s’"), conn_uri); - else - snprintf (errmsg, sizeof errmsg, _("cannot open libvirt connection")); - caml_invalid_argument (errmsg); - } - - /* Suppress default behaviour of printing errors to stderr. Note - * you can't set this to NULL to ignore errors; setting it to NULL - * restores the default error handler ... - */ - virConnSetErrorFunc (conn, NULL, ignore_errors); - - capabilities = virConnectGetCapabilities (conn); - if (!capabilities) { - err = virGetLastError (); - snprintf (errmsg, sizeof errmsg, - _("cannot get libvirt hypervisor capabilities: %s"), - err->message); - virConnectClose (conn); - caml_invalid_argument (errmsg); - } - - capabilitiesv = caml_copy_string (capabilities); - free (capabilities); - - virConnectClose (conn); - - CAMLreturn (capabilitiesv); -} - -value -v2v_domain_exists (value connv, value domnamev) -{ - CAMLparam2 (connv, domnamev); - const char *conn_uri = NULL; - const char *domname; - /* We have to assemble the error on the stack because a dynamic - * string couldn't be freed. - */ - char errmsg[ERROR_MESSAGE_LEN]; - virErrorPtr err; - virConnectPtr conn; - virDomainPtr dom; - int domain_exists; - - if (connv != Val_int (0)) - conn_uri = String_val (Field (connv, 0)); /* Some conn */ - - /* We have to call the default authentication handler, not least - * since it handles all the PolicyKit crap. However it also makes - * coding this simpler. - */ - conn = virConnectOpenAuth (conn_uri, virConnectAuthPtrDefault, - VIR_CONNECT_RO); - if (conn == NULL) { - if (conn_uri) - snprintf (errmsg, sizeof errmsg, - _("cannot open libvirt connection ‘%s’"), conn_uri); - else - snprintf (errmsg, sizeof errmsg, _("cannot open libvirt connection")); - caml_invalid_argument (errmsg); - } - - /* Suppress default behaviour of printing errors to stderr. Note - * you can't set this to NULL to ignore errors; setting it to NULL - * restores the default error handler ... - */ - virConnSetErrorFunc (conn, NULL, ignore_errors); - - /* Look up the domain. */ - domname = String_val (domnamev); - dom = virDomainLookupByName (conn, domname); - - if (dom) { - domain_exists = 1; - virDomainFree (dom); - } - else { - err = virGetLastError (); - if (err->code == VIR_ERR_NO_DOMAIN) - domain_exists = 0; - else { - snprintf (errmsg, sizeof errmsg, - _("cannot find libvirt domain ‘%s’: %s"), - domname, err->message); - virConnectClose (conn); - caml_invalid_argument (errmsg); - } - } - - virConnectClose (conn); - - CAMLreturn (Val_bool (domain_exists)); -} - -value -v2v_libvirt_get_version (value unitv) -{ - CAMLparam1 (unitv); - CAMLlocal1 (rv); - int major, minor, release; - /* We have to assemble the error on the stack because a dynamic - * string couldn't be freed. - */ - char errmsg[ERROR_MESSAGE_LEN]; - unsigned long ver; - virErrorPtr err; - - if (virGetVersion (&ver, NULL, NULL) == -1) { - err = virGetLastError (); - snprintf (errmsg, sizeof errmsg, - _("cannot get libvirt library version: %s"), - err->message); - caml_invalid_argument (errmsg); - } - - major = ver / 1000000UL; - minor = ver / 1000UL % 1000UL; - release = ver % 1000UL; - - rv = caml_alloc (3, 0); - Store_field (rv, 0, Val_int (major)); - Store_field (rv, 1, Val_int (minor)); - Store_field (rv, 2, Val_int (release)); - - CAMLreturn (rv); -} diff --git a/v2v/libvirt_utils.ml b/v2v/libvirt_utils.ml index efb186a2c..e6d315403 100644 --- a/v2v/libvirt_utils.ml +++ b/v2v/libvirt_utils.ml @@ -17,21 +17,92 @@ *) open Std_utils +open Tools_utils +open Common_gettext.Gettext -(* This module implements various [virsh]-like commands, but with - non-broken authentication handling. *) +(* This module provides helper methods on top of the Libvirt + module. *) -external dumpxml : ?password:string -> ?conn:string -> string -> string = "v2v_dumpxml" -let dumpxml ?password_file = - let password = Option.map read_first_line_from_file password_file in - dumpxml ?password +let auth_for_password_file ?password_file () = + let auth_fn creds = + let password = Option.map read_first_line_from_file password_file in + List.map ( + function + | { Libvirt.Connect.typ = Libvirt.Connect.CredentialPassphrase } -> password + | _ -> None + ) creds + in -external pool_dumpxml : ?conn:string -> string -> string = "v2v_pool_dumpxml" -external vol_dumpxml : ?conn:string -> string -> string -> string = "v2v_vol_dumpxml" + { + Libvirt.Connect.credtype = [ Libvirt.Connect.CredentialPassphrase ]; + cb = auth_fn; + } -external capabilities : ?conn:string -> unit -> string = "v2v_capabilities" +let get_domain conn name = + let dom = + try + Libvirt.Domain.lookup_by_uuid_string conn name + with + (* No such domain. *) + | Libvirt.Virterror { code = VIR_ERR_NO_DOMAIN } + (* Invalid UUID string. *) + | Libvirt.Virterror { code = VIR_ERR_INVALID_ARG; domain = VIR_FROM_DOMAIN } -> + (try + Libvirt.Domain.lookup_by_name conn name + with + Libvirt.Virterror { code = VIR_ERR_NO_DOMAIN; message } -> + error (f_"cannot find libvirt domain ‘%s’: %s") + name (Option.default "" message) + ) in + let uri = Libvirt.Connect.get_uri conn in + (* As a side-effect we check that the domain is shut down. Of course + * this is only appropriate for virt-v2v. (RHBZ#1138586) + *) + if not (String.is_prefix uri "test:") then ( + (match (Libvirt.Domain.get_info dom).Libvirt.Domain.state with + | InfoRunning | InfoBlocked | InfoPaused -> + error (f_"libvirt domain ‘%s’ is running or paused. It must be shut down in order to perform virt-v2v conversion") + (Libvirt.Domain.get_name dom) + | InfoNoState | InfoShutdown | InfoShutoff | InfoCrashed | InfoPMSuspended -> + () + ) + ); + dom -external domain_exists : ?conn:string -> string -> bool = "v2v_domain_exists" +let get_pool conn name = + try + Libvirt.Pool.lookup_by_uuid_string conn name + with + (* No such pool. *) + | Libvirt.Virterror { code = VIR_ERR_NO_STORAGE_POOL } + (* Invalid UUID string. *) + | Libvirt.Virterror { code = VIR_ERR_INVALID_ARG; domain = VIR_FROM_STORAGE } -> + (try + Libvirt.Pool.lookup_by_name conn name + with Libvirt.Virterror { code = VIR_ERR_NO_STORAGE_POOL; message } -> + error (f_"cannot find libvirt pool ‘%s’: %s\n\nUse ‘virsh pool-list --all’ to list all available pools, and ‘virsh pool-dumpxml <pool>’ to display details about a particular pool.\n\nTo set the pool which virt-v2v uses, add the ‘-os <pool>’ option.") + name (Option.default "" message) + ) -external libvirt_get_version : unit -> int * int * int - = "v2v_libvirt_get_version" +let get_volume pool name = + try + Libvirt.Volume.lookup_by_name pool name + with + (* No such volume. *) + | Libvirt.Virterror { code = VIR_ERR_NO_STORAGE_VOL; message } -> + error (f_"cannot find libvirt volume ‘%s’: %s") + name (Option.default "" message) + +let domain_exists conn dom = + try + ignore (Libvirt.Domain.lookup_by_name conn dom); + true + with + Libvirt.Virterror { code = VIR_ERR_NO_DOMAIN } -> false + +let libvirt_get_version () = + let v, _ = Libvirt.get_version () in + let v_major = v / 1000000 in + let v_minor = (v / 1000) mod 1000 in + let v_micro = v mod 1000 in + (v_major, v_minor, v_micro) diff --git a/v2v/libvirt_utils.mli b/v2v/libvirt_utils.mli index e6ba47ae8..91b2997ee 100644 --- a/v2v/libvirt_utils.mli +++ b/v2v/libvirt_utils.mli @@ -16,38 +16,35 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. *) -(** This module implements various [virsh]-like commands, but with - non-broken authentication handling. +(** This module provides helper methods on top of the [Libvirt] + module. *) - If you do [virsh dumpxml foo] and if the libvirt source (eg. ESX) - requires an interactive password, then virsh unhelpfully sends the - password prompt to stdout, which is the same place we would be - reading the XML from. This file works around this brokenness. *) +val auth_for_password_file : ?password_file:string -> unit -> Libvirt.Connect.auth +(** [auth_for_password_file ?password_file ()] returns a + {!Libvirt.Connect.auth} record to use when opening a new libvirt + connection with {!Libvirt.Connect.connect_auth} or + {!Libvirt.Connect.connect_auth_readonly}. The record will + authenticate using the password specified in the first line of + [?password_file], if specified. *) -val dumpxml : ?password_file:string -> ?conn:string -> string -> string -(** [dumpxml ?password_file ?conn dom] returns the libvirt XML of domain [dom]. - The optional [?conn] parameter is the libvirt connection URI. - [dom] may be a guest name or UUID. *) +val get_domain : Libvirt.rw Libvirt.Connect.t -> string -> Libvirt.rw Libvirt.Domain.t +(** [get_domain conn dom] returns the libvirt domain with the + specified [dom] name or UUID. [conn] is the libvirt + connection. *) -val pool_dumpxml : ?conn:string -> string -> string -(** [pool_dumpxml ?conn pool] returns the libvirt XML of pool [pool]. - The optional [?conn] parameter is the libvirt connection URI. - [pool] may be a pool name or UUID. *) +val get_pool : Libvirt.rw Libvirt.Connect.t -> string -> Libvirt.rw Libvirt.Pool.t +(** [get_pool conn pool] returns the libvirt pool with the + specified [pool] name or UUID. [conn] is the libvirt + connection. *) -val vol_dumpxml : ?conn:string -> string -> string -> string -(** [vol_dumpxml ?conn pool vol] returns the libvirt XML of volume [vol], - which is part of the pool [pool]. - The optional [?conn] parameter is the libvirt connection URI. - [pool] may be a pool name or UUID. *) +val get_volume : Libvirt.rw Libvirt.Pool.t -> string -> Libvirt.rw Libvirt.Volume.t +(** [get_volume pool vol] returns the libvirt volume with the + specified [vol] name or UUID, as part of the pool [pool]. *) -val capabilities : ?conn:string -> unit -> string -(** [capabilities ?conn ()] returns the libvirt capabilities XML. - The optional [?conn] parameter is the libvirt connection URI. *) - -val domain_exists : ?conn:string -> string -> bool -(** [domain_exists ?conn dom] returns a boolean indicating if the - the libvirt XML domain [dom] exists. - The optional [?conn] parameter is the libvirt connection URI. +val domain_exists : Libvirt.rw Libvirt.Connect.t -> string -> bool +(** [domain_exists conn dom] returns a boolean indicating if the + the libvirt XML domain [dom] exists. [conn] is the libvirt + connection. [dom] may be a guest name, but not a UUID. *) val libvirt_get_version : unit -> int * int * int diff --git a/v2v/output_libvirt.ml b/v2v/output_libvirt.ml index e110897c2..30857c572 100644 --- a/v2v/output_libvirt.ml +++ b/v2v/output_libvirt.ml @@ -71,8 +71,17 @@ class output_libvirt oc output_pool = object | Some uri -> sprintf "-o libvirt -oc %s -os %s" uri output_pool method prepare_targets source overlays _ _ _ _ = + (* Open the connection to libvirt. *) + let conn = Libvirt.Connect.connect ?name:oc () in + (* Get the capabilities from libvirt. *) - let xml = Libvirt_utils.capabilities ?conn:oc () in + let xml = + try + Libvirt.Connect.get_capabilities conn + with + Libvirt.Virterror { message } -> + error (f_"cannot get libvirt hypervisor capabilities: %s") + (Option.default "" message) in debug "libvirt capabilities XML:\n%s" xml; (* This just checks that the capabilities XML is well-formed, @@ -87,7 +96,7 @@ class output_libvirt oc output_pool = object 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 Libvirt_utils.domain_exists conn 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 @@ -99,7 +108,9 @@ class output_libvirt oc output_pool = object (* 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 xml = + let pool = Libvirt_utils.get_pool conn output_pool in + Libvirt.Pool.get_xml_desc (Libvirt.Pool.const pool) in let doc = Xml.parse_memory xml in let xpathctx = Xml.xpath_new_context doc in let xpath_string = xpath_string xpathctx in diff --git a/v2v/parse_libvirt_xml.ml b/v2v/parse_libvirt_xml.ml index d5d78d367..8f620331c 100644 --- a/v2v/parse_libvirt_xml.ml +++ b/v2v/parse_libvirt_xml.ml @@ -74,6 +74,17 @@ let create_curl_qemu_uri driver host port path = let parse_libvirt_xml ?conn xml = debug "libvirt xml is:\n%s" xml; + (* Create a default libvirt connection on request, to not open one + * in case there is no need to fetch more data (for example inspect + * the storage pools). + *) + let lazy_conn = + lazy (Libvirt.Connect.connect ()) in + let get_conn () = + match conn with + | None -> Lazy.force lazy_conn + | Some conn -> conn in + let doc = Xml.parse_memory xml in let xpathctx = Xml.xpath_new_context doc in let xpath_string = xpath_string xpathctx @@ -333,7 +344,10 @@ let parse_libvirt_xml ?conn xml = (match xpath_string "source/@pool", xpath_string "source/@volume" with | None, None | Some _, None | None, Some _ -> () | Some pool, Some vol -> - let xml = Libvirt_utils.vol_dumpxml ?conn pool vol in + let xml = + let pool = Libvirt_utils.get_pool (get_conn ()) pool in + let vol = Libvirt_utils.get_volume pool vol in + Libvirt.Volume.get_xml_desc (Libvirt.Volume.const vol) in let doc = Xml.parse_memory xml in let xpathctx = Xml.xpath_new_context doc in let xpath_string = Xpath_helpers.xpath_string xpathctx in @@ -511,3 +525,10 @@ let parse_libvirt_xml ?conn xml = s_nics = nics; }, disks) + +let parse_libvirt_domain conn guest = + let dom = Libvirt_utils.get_domain conn guest in + (* Use XmlSecure to get passwords (RHBZ#1174123). *) + let xml = Libvirt.Domain.get_xml_desc_flags dom [Libvirt.Domain.XmlSecure] in + let source, disks = parse_libvirt_xml ~conn xml in + source, disks, xml diff --git a/v2v/parse_libvirt_xml.mli b/v2v/parse_libvirt_xml.mli index 67a8ea96e..2d81e0d99 100644 --- a/v2v/parse_libvirt_xml.mli +++ b/v2v/parse_libvirt_xml.mli @@ -27,7 +27,16 @@ and parsed_source = | P_source_file of string (** <source file> *) | P_dont_rewrite (** s_qemu_uri is already set. *) -val parse_libvirt_xml : ?conn:string -> string -> Types.source * parsed_disk list +val parse_libvirt_domain : Libvirt.rw Libvirt.Connect.t -> string -> Types.source * parsed_disk list * string +(** [parse_libvirt_domain conn dom] loads the XML of the domain [dom] + from the libvirt connection [conn]. + The result is a tuple with a {!Types.source} structure, a list of + source disks, and the XML of the guest. + + {b Note} the [source.s_disks] field is an empty list. The caller + must map over the parsed disks and update the [source.s_disks] field. *) + +val parse_libvirt_xml : ?conn:Libvirt.rw Libvirt.Connect.t -> string -> Types.source * parsed_disk list (** Take libvirt XML and parse it into a {!Types.source} structure and a list of source disks. -- 2.20.1