On 11/22/22 16:47, Richard W.M. Jones wrote:
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(-)
I've skimmed this. It seems very useful and mostly okay, where "mostly
okay" qualifies *my level of understanding* of the code, and not the
code itself. There is a bit of code duplication with other parts of the
tree, but I think that's entirely OK: this is a new (experimental?)
tool, for further feature enablement, so intrusive refactorings are not
called for at this point, IMO. I suggest that we merge this ASAP and let
people start testing it.
One suggestion: migrate the nice info from the cover letter into this
actual commit message.
Acked-by: Laszlo Ersek <lersek(a)redhat.com>
Laszlo
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-...
=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