This tool can be used to estimate the disk space needed before doing a virt-v2v conversion.
It is a replacement for the old --print-estimate option which was dropped in virt-v2v 2.0 (commit 5828c9c7d5 "v2v: Remove --print-estimate option"). --- docs/Makefile.am | 15 ++ docs/test-v2v-docs.sh | 9 + docs/virt-v2v-inspector.pod | 252 +++++++++++++++++++ docs/virt-v2v.pod | 4 + configure.ac | 1 + Makefile.am | 3 +- inspector/Makefile.am | 129 ++++++++++ tests/Makefile.am | 2 + inspector/inspector.mli | 19 ++ inspector/inspector.ml | 472 ++++++++++++++++++++++++++++++++++++ inspector/dummy.c | 2 + tests/test-v2v-inspector.sh | 76 ++++++ .gitignore | 3 + run.in | 3 +- 14 files changed, 988 insertions(+), 2 deletions(-) diff --git a/docs/Makefile.am b/docs/Makefile.am index 3668fd4f0c..012c672294 100644 --- a/docs/Makefile.am +++ b/docs/Makefile.am @@ -24,6 +24,7 @@ EXTRA_DIST = \ virt-v2v-in-place.pod \ virt-v2v-input-vmware.pod \ virt-v2v-input-xen.pod \ + virt-v2v-inspector.pod \ virt-v2v-output-local.pod \ virt-v2v-output-openstack.pod \ virt-v2v-output-rhv.pod \ @@ -40,6 +41,7 @@ man_MANS = \ virt-v2v-in-place.1 \ virt-v2v-input-vmware.1 \ virt-v2v-input-xen.1 \ + virt-v2v-inspector.1 \ virt-v2v-output-local.1 \ virt-v2v-output-openstack.1 \ virt-v2v-output-rhv.1 \ @@ -53,6 +55,7 @@ noinst_DATA = \ $(top_builddir)/website/virt-v2v-in-place.1.html \ $(top_builddir)/website/virt-v2v-input-vmware.1.html \ $(top_builddir)/website/virt-v2v-input-xen.1.html \ + $(top_builddir)/website/virt-v2v-inspector.1.html \ $(top_builddir)/website/virt-v2v-output-local.1.html \ $(top_builddir)/website/virt-v2v-output-openstack.1.html \ $(top_builddir)/website/virt-v2v-output-rhv.1.html \ @@ -117,6 +120,18 @@ stamp-virt-v2v-input-xen.pod: virt-v2v-input-xen.pod $< touch $@ +virt-v2v-inspector.1 $(top_builddir)/website/virt-v2v-inspector.1.html: stamp-virt-v2v-inspector.pod + +stamp-virt-v2v-inspector.pod: virt-v2v-inspector.pod + $(PODWRAPPER) \ + --man virt-v2v-inspector.1 \ + --html $(top_builddir)/website/virt-v2v-inspector.1.html \ + --path $(top_srcdir)/common/options \ + --license GPLv2+ \ + --warning safe \ + $< + touch $@ + virt-v2v-output-local.1 $(top_builddir)/website/virt-v2v-output-local.1.html: stamp-virt-v2v-output-local.pod stamp-virt-v2v-output-local.pod: virt-v2v-output-local.pod diff --git a/docs/test-v2v-docs.sh b/docs/test-v2v-docs.sh index 92ae39ee57..c0de5a20ce 100755 --- a/docs/test-v2v-docs.sh +++ b/docs/test-v2v-docs.sh @@ -75,3 +75,12 @@ $srcdir/../podcheck.pl virt-v2v-in-place.pod virt-v2v-in-place \ --oo,\ --op,\ --os + +$srcdir/../podcheck.pl virt-v2v-inspector.pod virt-v2v-inspector \ + --path $srcdir/../common/options \ + --ignore=\ +--ic,\ +--if,\ +--io,\ +--ip,\ +--it diff --git a/docs/virt-v2v-inspector.pod b/docs/virt-v2v-inspector.pod new file mode 100644 index 0000000000..d2f0b66e4f --- /dev/null +++ b/docs/virt-v2v-inspector.pod @@ -0,0 +1,252 @@ +=head1 NAME + +virt-v2v-inspector - Estimate disk space needed before virt-v2v conversion + +=head1 SYNOPSIS + + virt-v2v-inspector [-i* options] guest + +=head1 DESCRIPTION + +Virt-v2v-inspector is a companion tool for L<virt-v2v(1)> which can be +used before conversion to estimate the number of output disks and disk +space that will be required to complete the virt-v2v conversion. The +common use for this is to preallocate target disks on management +systems that need this (like Kubevirt). + +This manual page only documents the estimation feature, not all of the +I<-i*> options which are the same as virt-v2v. You should read +L<virt-v2v(1)> first. + +=head2 Selecting the input guest + +You can run virt-v2v-inspector with the same I<-i*> options as +virt-v2v. (Don't use any I<-o*> options). This will select the guest +that you want to estimate. + +For example to estimate the space required for a guest in a stored +local disk called F<filename.img> you could do: + + virt-v2v-inspector -i disk filename.img + +=head2 Output + +The output from this tool is an XML document (written to stdout). + +=over 4 + +=item * + +Fields which are annotated with an C<estimated='true'> attribute are +estimated. Virt-v2v cannot always know exactly the final size of some +things, such as the exact real size of the output disk, since there +might be small perturbations between runs. Estimates are usually very +close to the final values. + +=item * + +Elements (including sub-trees) which are annotated with an +C<informational='true'> attribute are for information only. These +elements might be changed or removed in future versions. If you would +like to rely on this data in your program please contact the +developers. + +=item * + +Numbers representing sizes are always given in bytes. + +=back + + <?xml version='1.0' encoding='utf-8'?> + <v2v-inspection> + <program>virt-v2v-inspector</program> + <package>virt-v2v</package> + <version>2.1.9</version> + +The E<lt>programE<gt>, E<lt>packageE<gt> and E<lt>versionE<gt> +elements refer to the current version of virt-v2v-inspector and are +useful for debugging. Make sure you use the same version of +virt-v2v-inspector and virt-v2v. + + <disks> + <disk index='0'> + <virtual-size>6442450944</virtual-size> + <allocated estimated='true'>1400897536</allocated> + </disk> + <disk index='1'> + <virtual-size>6442450944</virtual-size> + <allocated estimated='true'>45131520</allocated> + </disk> + </disks> + +The E<lt>disksE<gt> element lists information about each guest disk. +The example virtual machine above has two disks. +E<lt>virtual-sizeE<gt> describes the size of the disk as seen from +inside the guest, while E<lt>allocatedE<gt> is an estimate of how much +storage will be needed on the host after conversion. This is assuming +you use S<I<-oa sparse>> - see the notes below. + + <operatingsystem> + <name>linux</name> + <distro>fedora</distro> + <osinfo>fedora32</osinfo> + <arch>x86_64</arch> + [...] + </operatingsystem> + +The E<lt>operatingsystemE<gt> element lists information about the +guest operating system gleaned during conversion, in a manner similar +to the L<virt-inspector(1)> tool from guestfs-tools. + +=head2 Output allocation mode and output format + +Virt-v2v supports selecting the output allocation mode (I<-oa> option) +and output format (I<-of> option, eg. S<I<-of qcow2>>). Since it is +difficult to predict the effect of these options on the actual space +occupied by the final image this tool does not account for them. + +As a rule of thumb: + +=over 4 + +=item S<virt-v2v -oa preallocated> + +causes the disk images on the target to consume their full virtual +size (excluding the effect of zero allocations will depends so much on +the underlying storage that it is often hard even for experts to +predict). + +=item S<virt-v2v -of qcow2> + +uses the QCOW2 format where supported which means that the apparent +size of the file will be equal to its sparse size, but otherwise +should not affect estimates very much. + +=back + +=head1 OPTIONS + +=over 4 + +=item B<--help> + +Display help. + +=item B<-v> + +=item B<--verbose> + +Enable verbose messages for debugging. + +=item B<-V> + +=item B<--version> + +Display version number and exit. + +=item B<-x> + +Enable tracing of libguestfs API calls. + +=item B<-i> ... + +=item B<-ic> ... + +=item B<-if> ... + +=item B<-io> ... + +=item B<-ip> ... + +=item B<-it> ... + +All of the I<-i*> options supported by virt-v2v and also supported by +virt-v2v-inspector. + +=item B<-b> ... + +=item B<--bridge> ... + +=item B<--colors> + +=item B<--colours> + +=item B<--echo-keys> + +=item B<--key> ... + +=item B<--keys-from-stdin> + +=item B<--mac> ... + +=item B<--machine-readable> + +=item B<--machine-readable>=format + +=item B<-n> ... + +=item B<--network> ... + +=item B<-q> + +=item B<--quiet> + +=item B<--root> ... + +=item B<--wrap> + +These options work in the same way as the equivalent virt-v2v options. + +=back + +=head1 FILES + +Files used are the same as for virt-v2v. See L<virt-v2v(1)/FILES>. + +=head1 ENVIRONMENT VARIABLES + +Environment variables used are the same as for virt-v2v. See +L<virt-v2v(1)/ENVIRONMENT VARIABLES>. + +=head1 SEE ALSO + +L<virt-v2v(1)>, +L<virt-p2v(1)>, +L<virt-inspector(1)>, +L<guestfs(3)>, +L<guestfish(1)>, +L<qemu-img(1)>, +L<nbdkit(1)>, +L<http://libguestfs.org/>. + +=head1 AUTHORS + +Matthew Booth + +Cédric Bosdonnat + +Laszlo Ersek + +Tomáš Golembiovský + +Shahar Havivi + +Richard W.M. Jones + +Roman Kagan + +Mike Latimer + +Nir Soffer + +Pino Toscano + +Xiaodai Wang + +Ming Xie + +Tingting Zheng + +=head1 COPYRIGHT + +Copyright (C) 2009-2022 Red Hat Inc. diff --git a/docs/virt-v2v.pod b/docs/virt-v2v.pod index 4901c8407f..4f3d977a15 100644 --- a/docs/virt-v2v.pod +++ b/docs/virt-v2v.pod @@ -21,6 +21,9 @@ There is also a companion front-end called L<virt-p2v(1)> which comes as an ISO, CD or PXE image that can be booted on physical machines to virtualize those machines (physical to virtual, or p2v). +To estimate the disk space needed before conversion, see +L<virt-v2v-inspector(1)>. + For in-place conversion, there is a separate tool called L<virt-v2v-in-place(1)>. @@ -1624,6 +1627,7 @@ L<https://rwmj.wordpress.com/2015/09/18/importing-kvm-guests-to-ovirt-or-rhev/#c =head1 SEE ALSO L<virt-p2v(1)>, +L<virt-v2v-inspector(1)>, L<virt-v2v-in-place(1)>, L<virt-customize(1)>, L<virt-df(1)>, diff --git a/configure.ac b/configure.ac index b2396781d6..f8e2836551 100644 --- a/configure.ac +++ b/configure.ac @@ -146,6 +146,7 @@ AC_CONFIG_FILES([Makefile gnulib/lib/Makefile in-place/Makefile input/Makefile + inspector/Makefile lib/Makefile lib/config.ml output/Makefile diff --git a/Makefile.am b/Makefile.am index cec68d76ce..16cd5f36d9 100644 --- a/Makefile.am +++ b/Makefile.am @@ -42,6 +42,7 @@ SUBDIRS += input SUBDIRS += output SUBDIRS += convert SUBDIRS += v2v +SUBDIRS += inspector SUBDIRS += in-place SUBDIRS += tests @@ -111,7 +112,7 @@ po/POTFILES: configure.ac po/POTFILES-ml: configure.ac rm -f $@ $@-t cd $(srcdir); \ - find common/ml* lib in-place input output v2v -name '*.ml' | \ + find common/ml* lib in-place input inspector output v2v -name '*.ml' | \ grep -v '^common/mlprogress/' | \ grep -v '^common/mlvisit/' | \ grep -v '^lib/config.ml$$' | \ diff --git a/inspector/Makefile.am b/inspector/Makefile.am new file mode 100644 index 0000000000..30e6a297fa --- /dev/null +++ b/inspector/Makefile.am @@ -0,0 +1,129 @@ +# libguestfs virt-v2v-inspector tool +# Copyright (C) 2009-2022 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. + +include $(top_srcdir)/subdir-rules.mk + +EXTRA_DIST = \ + $(SOURCES_MLI) \ + $(SOURCES_ML) \ + $(SOURCES_C) + +SOURCES_MLI = \ + inspector.mli + +SOURCES_ML = \ + inspector.ml + +SOURCES_C = \ + dummy.c + +bin_PROGRAMS = virt-v2v-inspector + +virt_v2v_inspector_SOURCES = $(SOURCES_C) +virt_v2v_inspector_CPPFLAGS = \ + -DCAML_NAME_SPACE \ + -I. \ + -I$(top_builddir) \ + -I$(shell $(OCAMLC) -where) \ + -I$(top_srcdir)/lib +virt_v2v_inspector_CFLAGS = \ + -pthread \ + $(WARN_CFLAGS) $(WERROR_CFLAGS) \ + $(LIBGUESTFS_CFLAGS) \ + $(LIBVIRT_CFLAGS) \ + $(LIBOSINFO_CFLAGS) + +BOBJECTS = $(SOURCES_ML:.ml=.cmo) +XOBJECTS = $(BOBJECTS:.cmo=.cmx) + +OCAMLPACKAGES = \ + -package str,unix,guestfs,libvirt,nbd \ + -I $(top_builddir)/common/utils/.libs \ + -I $(top_builddir)/common/qemuopts/.libs \ + -I $(top_builddir)/gnulib/lib/.libs \ + -I $(top_builddir)/lib \ + -I $(top_builddir)/input \ + -I $(top_builddir)/convert \ + -I $(top_builddir)/common/mlstdutils \ + -I $(top_builddir)/common/mlutils \ + -I $(top_builddir)/common/mlgettext \ + -I $(top_builddir)/common/mlpcre \ + -I $(top_builddir)/common/mlxml \ + -I $(top_builddir)/common/mltools \ + -I $(top_builddir)/common/mlcustomize \ + -I $(top_builddir)/common/mlv2v +if HAVE_OCAML_PKG_GETTEXT +OCAMLPACKAGES += -package gettext-stub +endif + +OCAMLCLIBS = \ + -pthread \ + -lqemuopts \ + $(LIBGUESTFS_LIBS) \ + $(LIBVIRT_LIBS) \ + $(LIBXML2_LIBS) \ + $(JANSSON_LIBS) \ + $(LIBOSINFO_LIBS) \ + $(LIBINTL) \ + $(LIBNBD_LIBS) \ + -lgnu + +OCAMLFLAGS = $(OCAML_FLAGS) $(OCAML_WARN_ERROR) -ccopt '$(CFLAGS)' + +if !HAVE_OCAMLOPT +OBJECTS = $(BOBJECTS) +else +OBJECTS = $(XOBJECTS) +endif + +OCAMLLINKFLAGS = \ + mlstdutils.$(MLARCHIVE) \ + mlgettext.$(MLARCHIVE) \ + mlpcre.$(MLARCHIVE) \ + mlxml.$(MLARCHIVE) \ + mlcutils.$(MLARCHIVE) \ + mltools.$(MLARCHIVE) \ + mllibvirt.$(MLARCHIVE) \ + mlcustomize.$(MLARCHIVE) \ + mlv2v.$(MLARCHIVE) \ + mlv2vlib.$(MLARCHIVE) \ + mlconvert.$(MLARCHIVE) \ + mlinput.$(MLARCHIVE) \ + $(LINK_CUSTOM_OCAMLC_ONLY) + +virt_v2v_inspector_DEPENDENCIES = \ + $(OBJECTS) \ + $(top_builddir)/input/mlinput.$(MLARCHIVE) \ + $(top_builddir)/convert/mlconvert.$(MLARCHIVE) \ + $(top_builddir)/lib/mlv2vlib.$(MLARCHIVE) \ + $(top_srcdir)/ocaml-link.sh +virt_v2v_inspector_LINK = \ + $(top_srcdir)/ocaml-link.sh -cclib '$(OCAMLCLIBS)' -- \ + $(OCAMLFIND) $(BEST) $(OCAMLFLAGS) $(OCAMLPACKAGES) $(OCAMLLINKFLAGS) \ + $(OBJECTS) -o $@ + +# Data directory. + +virttoolsdatadir = $(datadir)/virt-tools + +# Dependencies. +.depend: \ + $(srcdir)/*.mli \ + $(srcdir)/*.ml \ + $(filter %.ml,$(BUILT_SOURCES)) + $(top_builddir)/ocaml-dep.sh $^ +-include .depend diff --git a/tests/Makefile.am b/tests/Makefile.am index fb068624c7..de3f1fe9e2 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -78,6 +78,7 @@ TESTS = \ test-v2v-i-disk.sh \ test-v2v-i-ova.sh \ test-v2v-in-place.sh \ + test-v2v-inspector.sh \ test-v2v-mac.sh \ test-v2v-machine-readable.sh \ test-v2v-networks-and-bridges.sh \ @@ -235,6 +236,7 @@ EXTRA_DIST += \ test-v2v-i-vmx-6.vmx \ test-v2v-i-vmx-7.vmx \ test-v2v-in-place.sh \ + test-v2v-inspector.sh \ test-v2v-it-vddk-io-query.sh \ test-v2v-machine-readable.sh \ test-v2v-mac-expected.xml \ diff --git a/inspector/inspector.mli b/inspector/inspector.mli new file mode 100644 index 0000000000..af7cc31cb3 --- /dev/null +++ b/inspector/inspector.mli @@ -0,0 +1,19 @@ +(* virt-v2v-in-place + * Copyright (C) 2009-2022 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. + *) + +(* Nothing is exported. *) diff --git a/inspector/inspector.ml b/inspector/inspector.ml new file mode 100644 index 0000000000..0ded5d62ae --- /dev/null +++ b/inspector/inspector.ml @@ -0,0 +1,472 @@ +(* virt-v2v-inspector + * Copyright (C) 2009-2022 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 Common_gettext.Gettext +open Getopt.OptionName + +open Types +open Utils +open DOM + +(* Matches --mac command line parameters. *) +let mac_re = PCRE.compile ~anchored:true "([[:xdigit:]]{2}:[[:xdigit:]]{2}:[[:xdigit:]]{2}:[[:xdigit:]]{2}:[[:xdigit:]]{2}:[[:xdigit:]]{2}):(network|bridge|ip):(.*)" +let mac_ip_re = PCRE.compile ~anchored:true "([[:xdigit:]]|:|\\.)+" + +let rec main () = + let set_string_option_once optname optref arg = + match !optref with + | Some _ -> + error (f_"%s option used more than once on the command line") optname + | None -> + optref := Some arg + in + + let bandwidth = ref None in + let bandwidth_file = ref None in + let input_conn = ref None in + let input_format = ref None in + let input_password = ref None in + let input_transport = ref None in + + let input_options = ref [] in + let io_query = ref false in + let set_input_option_compat k v = + List.push_back input_options (k, v) + in + let set_input_option option = + if option = "?" then io_query := true + else ( + let k, v = String.split "=" option in + set_input_option_compat k v + ) + in + + let network_map = Networks.create () in + let static_ips = ref [] in + let rec add_network str = + match String.split ":" str with + | "", "" -> + error (f_"invalid -n/--network parameter") + | out, "" | "", out -> + Networks.add_default_network network_map out + | in_, out -> + Networks.add_network network_map in_ out + and add_bridge str = + match String.split ":" str with + | "", "" -> + error (f_"invalid -b/--bridge parameter") + | out, "" | "", out -> + Networks.add_default_bridge network_map out + | in_, out -> + Networks.add_bridge network_map in_ out + and add_mac str = + if not (PCRE.matches mac_re str) then + error (f_"cannot parse --mac \"%s\" parameter") str; + let mac = PCRE.sub 1 and out = PCRE.sub 3 in + match PCRE.sub 2 with + | "network" -> + Networks.add_mac network_map mac Network out + | "bridge" -> + Networks.add_mac network_map mac Bridge out + | "ip" -> + (match String.nsplit "," out with + | [] -> error (f_"invalid --mac ip option") + | [ip] -> add_static_ip mac ip None None [] + | [ip; gw] -> add_static_ip mac ip (Some gw) None [] + | ip :: gw :: len :: nameservers -> + add_static_ip mac ip (Some gw) (Some len) nameservers + ) + | _ -> assert false + and add_static_ip if_mac_addr if_ip_address if_default_gateway + if_prefix_length_str if_nameservers = + (* Check the IP addresses and prefix length are sensible. This + * is only a very simple test that they are sane, since IP addresses + * come in too many valid forms to check thoroughly. + *) + let rec error_unless_ip_addr what addr = + if not (PCRE.matches mac_ip_re addr) then + error (f_"cannot parse --mac ip %s: doesn’t look like “%s” is an IP address") what addr + in + error_unless_ip_addr "ipaddr" if_ip_address; + Option.may (error_unless_ip_addr "gw") if_default_gateway; + List.iter (error_unless_ip_addr "nameserver") if_nameservers; + let if_prefix_length = + match if_prefix_length_str with + | None -> None + | Some len -> + let len = + try int_of_string len with + | Failure _ -> error (f_"cannot parse --mac ip prefix length field as an integer: %s") len in + if len < 0 || len > 128 then + error (f_"--mac ip prefix length field is out of range"); + Some len in + List.push_back static_ips + { if_mac_addr; if_ip_address; if_default_gateway; + if_prefix_length; if_nameservers } + in + + let root_choice = ref AskRoot in + let set_root_choice = function + | "ask" -> root_choice := AskRoot + | "single" -> root_choice := SingleRoot + | "first" -> root_choice := FirstRoot + | dev when String.is_prefix dev "/dev/" -> root_choice := RootDev dev + | s -> + error (f_"unknown --root option: %s") s + in + + let input_mode = ref `Not_set in + let set_input_mode mode = + if !input_mode <> `Not_set then + error (f_"%s option used more than once on the command line") "-i"; + match mode with + | "disk" | "local" -> input_mode := `Disk + | "libvirt" -> input_mode := `Libvirt + | "libvirtxml" -> input_mode := `LibvirtXML + | "ova" -> input_mode := `OVA + | "vmx" -> input_mode := `VMX + | s -> + error (f_"unknown -i option: %s") s + in + + let argspec = [ + [ S 'b'; L"bridge" ], Getopt.String ("in:out", add_bridge), + s_"Map bridge ‘in’ to ‘out’"; + [ S 'i' ], Getopt.String ("disk|libvirt|libvirtxml|ova|vmx", set_input_mode), + s_"Set input mode (default: libvirt)"; + [ M"ic" ], Getopt.String ("uri", set_string_option_once "-ic" input_conn), + s_"Libvirt URI"; + [ M"if" ], Getopt.String ("format", set_string_option_once "-if" input_format), + s_"Input format"; + [ M"io" ], Getopt.String ("option[=value]", set_input_option), + s_"Set option for input mode"; + [ M"ip" ], Getopt.String ("filename", set_string_option_once "-ip" input_password), + s_"Use password from file to connect to input hypervisor"; + [ M"it" ], Getopt.String ("transport", set_string_option_once "-it" input_transport), + s_"Input transport"; + [ L"mac" ], Getopt.String ("mac:network|bridge|ip:out", add_mac), + s_"Map NIC to network or bridge or assign static IP"; + [ S 'n'; L"network" ], Getopt.String ("in:out", add_network), + s_"Map network ‘in’ to ‘out’"; + [ L"root" ], Getopt.String ("ask|... ", set_root_choice), + s_"How to choose root filesystem"; + ] in + let args = ref [] in + let anon_fun s = List.push_front s args in + let usage_msg = + sprintf (f_"\ +%s: estimate disk space needed before virt-v2v conversion + +virt-v2v-inspector -i disk disk.img + +A short summary of the options is given below. For detailed help please +read the man page virt-v2v-inspector(1). +") + prog in + let opthandle = create_standard_options argspec ~anon_fun ~key_opts:true ~machine_readable:true usage_msg in + Getopt.parse opthandle.getopt; + + (* Print the version, easier than asking users to tell us. *) + debug "%s: %s %s (%s)" + prog Config.package_name Config.package_version_full + Config.host_cpu; + + (* Print the libvirt version if debugging. *) + if verbose () then ( + let major, minor, release = Libvirt_utils.libvirt_get_version () in + debug "libvirt version: %d.%d.%d" major minor release + ); + + (* Create the v2v directory to control conversion. *) + let v2vdir = create_v2v_directory () in + + (* Dereference the arguments. *) + let args = List.rev !args in + let input_conn = !input_conn in + let input_mode = !input_mode in + let input_transport = + match !input_transport with + | None -> None + | Some "ssh" -> Some `SSH + | Some "vddk" -> Some `VDDK + | Some transport -> + error (f_"unknown input transport ‘-it %s’") transport in + let root_choice = !root_choice in + let static_ips = !static_ips in + + (* No arguments and machine-readable mode? Print out some facts + * about what this binary supports. + *) + (match args, machine_readable () with + | [], Some { pr } -> + pr "virt-v2v-inspector\n"; + pr "libguestfs-rewrite\n"; + pr "colours-option\n"; + pr "io\n"; + pr "mac-option\n"; + pr "mac-ip-option\n"; + pr "input:disk\n"; + pr "input:libvirt\n"; + pr "input:libvirtxml\n"; + pr "input:ova\n"; + pr "input:vmx\n"; + pr "convert:linux\n"; + pr "convert:windows\n"; + List.iter (pr "ovf:%s\n") Create_ovf.ovf_flavours; + exit 0 + | _, _ -> () + ); + + (* Get the input module. *) + let (module Input_module) = + match input_mode with + | `Disk -> (module Input_disk.Disk : Input.INPUT) + | `LibvirtXML -> (module Input_libvirt.LibvirtXML) + | `OVA -> (module Input_ova.OVA) + | `VMX -> (module Input_vmx.VMX) + | `Not_set | `Libvirt -> + match input_conn with + | None -> (module Input_libvirt.Libvirt_) + | Some orig_uri -> + let { Xml.uri_server = server; uri_scheme = scheme } = + try Xml.parse_uri orig_uri + with Invalid_argument msg -> + error (f_"could not parse '-ic %s'. Original error message was: %s") + orig_uri msg in + + match server, scheme, input_transport with + | None, _, _ + | Some "", _, _ (* Not a remote URI. *) + + | Some _, None, _ (* No scheme? *) + | Some _, Some "", _ -> + (module Input_libvirt.Libvirt_) + + (* vCenter over https. *) + | Some server, Some ("esx"|"gsx"|"vpx"), None -> + (module Input_vcenter_https.VCenterHTTPS) + + (* vCenter or ESXi using nbdkit vddk plugin *) + | Some server, Some ("esx"|"gsx"|"vpx"), Some `VDDK -> + (module Input_vddk.VDDK) + + (* Xen over SSH *) + | Some server, Some "xen+ssh", _ -> + (module Input_xen_ssh.XenSSH) + + (* Old virt-v2v also supported qemu+ssh://. However I am + * deliberately not supporting this in new virt-v2v. Don't + * use virt-v2v if a guest already runs on KVM. + *) + + (* Unknown remote scheme. *) + | 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; + (module Input_libvirt.Libvirt_) in + + let input_options = { + Input.bandwidth = + (match !bandwidth, !bandwidth_file with + | None, None -> None + | Some rate, None -> Some (StaticBandwidth rate) + | rate, Some filename -> Some (DynamicBandwidth (rate, filename))); + input_conn = input_conn; + input_format = !input_format; + input_options = !input_options; + input_password = !input_password; + input_transport = input_transport; + (* This must always be true so that we do not modify the + * source. This is set to [false] by in-place mode. + *) + read_only = true; + } in + + (* If -io ? then we want to query input options supported in this mode. *) + if !io_query then ( + Input_module.query_input_options (); + exit 0 + ); + + (* Get the conversion options. *) + let conv_options = { + Convert.keep_serial_console = true; + ks = opthandle.ks; + network_map; + root_choice; + static_ips; + } in + + (* Before starting the input module, check there is sufficient + * free space in the temporary directory on the host. + *) + check_host_free_space (); + + (* Start the input module (runs an NBD server in the background). *) + message (f_"Setting up the source: %s") + (Input_module.to_string input_options args); + let source = Input_module.setup v2vdir input_options args in + + (* Do the conversion. *) + with_open_out (v2vdir // "convert") (fun _ -> ()); + let inspect, _ = Convert.convert v2vdir conv_options source in + unlink (v2vdir // "convert"); + + (* Debug the v2vdir. *) + if verbose () then ( + let cmd = sprintf "ls -alZ %s 1>&2" (quote v2vdir) in + ignore (Sys.command cmd) + ); + + (* Dump out the information. *) + let doc = inspector_xml v2vdir inspect in + DOM.doc_to_chan Stdlib.stdout doc; + + message (f_"Finishing off"); + (* As the last thing, write a file indicating success before + * we exit (so before we kill the helpers). The helpers may + * use the presence or absence of the file to determine if + * on-success or on-fail cleanup is required. + *) + with_open_out (v2vdir // "done") (fun _ -> ()) + +(* Conversion can fail or hang if there is insufficient free space in + * the large temporary directory. Some input modules use large_tmpdir + * to unpack OVAs or store qcow2 overlays and some output modules + * use it to store temporary files. In addition the 500 MB guestfs + * appliance may be created there. (RHBZ#1316479, RHBZ#2051394) + *) +and check_host_free_space () = + let free_space = StatVFS.free_space (StatVFS.statvfs large_tmpdir) in + debug "check_host_free_space: large_tmpdir=%s free_space=%Ld" + large_tmpdir free_space; + if free_space < 1_073_741_824L then + error (f_"insufficient free space in the conversion server temporary directory %s (%s).\n\nEither free up space in that directory, or set the LIBGUESTFS_CACHEDIR environment variable to point to another directory with more than 1GB of free space.\n\nSee also the virt-v2v(1) manual, section \"Minimum free space check in the host\".") + large_tmpdir (human_size free_space) + +(* This is a copy of {!Output.get_disks}. *) +and get_disks dir = + let rec loop acc i = + let socket = sprintf "%s/in%d" dir i in + if Sys.file_exists socket then ( + let size = Utils.with_nbd_connect_unix ~socket NBD.get_size in + loop ((i, size) :: acc) (i+1) + ) + else + List.rev acc + in + loop [] 0 + +(* This is like {!Utils.get_disk_allocated} but works on the input disks. *) +and get_input_disk_allocated dir i = + let socket = sprintf "%s/in%d" dir i + and alloc_ctx = "base:allocation" in + with_nbd_connect_unix ~socket ~meta_contexts:[alloc_ctx] + (fun nbd -> + if NBD.can_meta_context nbd alloc_ctx then ( + (* Get the list of extents, using a 2GiB chunk size as hint. *) + let size = NBD.get_size nbd + and allocated = ref 0_L + and fetch_offset = ref 0_L in + while !fetch_offset < size do + let remaining = size -^ !fetch_offset in + let fetch_size = min 0x8000_0000_L remaining in + NBD.block_status nbd fetch_size !fetch_offset + (fun ctx offset entries err -> + assert (ctx = alloc_ctx); + for i = 0 to Array.length entries / 2 - 1 do + let len = entries.(i * 2) + and typ = entries.(i * 2 + 1) in + assert (len > 0_L); + if typ &^ 1_L = 0_L then + allocated := !allocated +^ len; + fetch_offset := !fetch_offset +^ len + done; + 0 + ) + done; + Some !allocated + ) else None + ) + +(* This is where we construct the final XML document based on + * these inputs: + * - Global configuration like the version of v2v etc. + * - The NBD input sockets: v2vdir // "in0", "in1", etc + * - The inspection data (Types.inspect) + *) +and inspector_xml v2vdir inspect = + let body = ref [] in + + (* Record the version of virt-v2v etc, mainly for debugging. *) + List.push_back_list body [ + Comment generated_by; + e "program" [] [PCData "virt-v2v-inspector"]; + e "package" [] [PCData Config.package_name]; + e "version" [] [PCData Config.package_version]; + ]; + + (* The disks. *) + let disks = ref [] in + + List.iter ( + fun (i, virtual_size) -> + let elems = ref [] in + List.push_back elems (e "virtual-size" [] + [PCData (Int64.to_string virtual_size)]); + (match get_input_disk_allocated v2vdir i with + | None -> () + | Some real_size -> + List.push_back elems (e "allocated" [ "estimated", "true" ] + [PCData (Int64.to_string real_size)]) + ); + + List.push_back disks (e "disk" [ "index", string_of_int i ] !elems) + ) (get_disks v2vdir); + List.push_back body (e "disks" [] !disks); + + (* The inspection data. *) + (* NB: Keep these field names compatible with virt-inspector! *) + let os = ref [] in + List.push_back os (e "name" [] [PCData inspect.i_type]); + List.push_back os (e "distro" [] [PCData inspect.i_distro]); + List.push_back os (e "osinfo" [] [PCData inspect.i_osinfo]); + List.push_back os (e "arch" [] [PCData inspect.i_arch]); + List.push_back os (e "major_version" [] + [PCData (string_of_int inspect.i_major_version)]); + List.push_back os (e "minor_version" [] + [PCData (string_of_int inspect.i_minor_version)]); + if inspect.i_package_format <> "" then + List.push_back os (e "package_format" [] + [PCData inspect.i_package_format]); + if inspect.i_package_management <> "" then + List.push_back os (e "package_management" [] + [PCData inspect.i_package_management]); + if inspect.i_product_name <> "" then + List.push_back os (e "product_name" [] [PCData inspect.i_product_name]); + List.push_back body (e "operatingsystem" [] !os); + + (* Construct the final document. *) + (doc "v2v-inspection" [] !body : DOM.doc) + +let () = run_main_and_handle_errors main diff --git a/inspector/dummy.c b/inspector/dummy.c new file mode 100644 index 0000000000..ebab6198cd --- /dev/null +++ b/inspector/dummy.c @@ -0,0 +1,2 @@ +/* Dummy source, to be used for OCaml-based tools with no C sources. */ +enum { foo = 1 }; diff --git a/tests/test-v2v-inspector.sh b/tests/test-v2v-inspector.sh new file mode 100755 index 0000000000..52406ddee1 --- /dev/null +++ b/tests/test-v2v-inspector.sh @@ -0,0 +1,76 @@ +#!/bin/bash - +# libguestfs virt-v2v test script +# Copyright (C) 2014-2022 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 virt-v2v-inspector. + +unset CDPATH +export LANG=C +set -e + +source ./functions.sh +set -e +set -x + +skip_if_skipped +requires test -f ../test-data/phony-guests/windows.img + +img="$abs_top_builddir/test-data/phony-guests/windows.img" + +export VIRT_TOOLS_DATA_DIR="$srcdir/../test-data/fake-virt-tools" +export VIRTIO_WIN="$srcdir/../test-data/fake-virtio-win" + +d=$PWD/test-v2v-inspector.d +rm -rf $d +cleanup_fn rm -r $d +mkdir $d + +out="$d/out" + +libvirt_xml="$d/test.xml" +rm -f $libvirt_xml +n=windows +cat > $libvirt_xml <<EOF +<node> + <domain type='test'> + <name>$n</name> + <memory>1048576</memory> + <os> + <type>hvm</type> + <boot dev='hd'/> + </os> + <devices> + <disk type='file' device='disk'> + <driver name='qemu' type='raw'/> + <source file='$img'/> + <target dev='vda' bus='virtio'/> + </disk> + </devices> + </domain> +</node> +EOF + +$VG virt-v2v-inspector --quiet --debug-gc -i libvirt -ic "test://$libvirt_xml" $n > $out +cat $out + +# Expect certain elements to be present. +grep '^<v2v-inspection' $out +grep '<program>virt-v2v-inspector</program>' $out +grep '<disks>' $out +grep "<disk index='0'>" $out +grep '<distro>windows</distro>' $out +grep '<osinfo>win7</osinfo>' $out diff --git a/.gitignore b/.gitignore index 62541b8980..655c794ee7 100644 --- a/.gitignore +++ b/.gitignore @@ -51,6 +51,7 @@ Makefile.in /docs/virt-v2v-in-place.1 /docs/virt-v2v-input-vmware.1 /docs/virt-v2v-input-xen.1 +/docs/virt-v2v-inspector.1 /docs/virt-v2v-output-local.1 /docs/virt-v2v-output-openstack.1 /docs/virt-v2v-output-rhv.1 @@ -62,6 +63,8 @@ Makefile.in /in-place/.depend /in-place/virt-v2v-in-place /input/.depend +/inspector/.depend +/inspector/virt-v2v-inspector /installcheck.sh /install-sh /libtool diff --git a/run.in b/run.in index 69936a6e2b..c75e4e0f0f 100755 --- a/run.in +++ b/run.in @@ -67,9 +67,10 @@ export LIBGUESTFS_CACHEDIR="$b/tmp" mkdir -p "$b/tmp" chcon --reference=/tmp "$b/tmp" 2>/dev/null ||: -# Set the PATH to contain the virt-v2v and virt-v2v-in-place binaries. +# Set the PATH to contain the virt-v2v and other binaries. prepend PATH "$b/v2v" prepend PATH "$b/in-place" +prepend PATH "$b/inspector" export PATH # This is a cheap way to find some use-after-free and uninitialized -- 2.37.0.rc2 _______________________________________________ Libguestfs mailing list Libguestfs@redhat.com https://listman.redhat.com/mailman/listinfo/libguestfs