Richard W.M. Jones
2014-Apr-09 13:50 UTC
[Libguestfs] [PATCH v3 NOT TO BE APPLIED] New tool: virt-v2v.
This is a rewrite of the original virt-v2v tool. The original was written by Matt Booth et al in Perl between 2009 and 2013. --- .gitignore | 5 + Makefile.am | 6 +- configure.ac | 3 +- fish/guestfish.pod | 1 + po/POTFILES | 2 + po/POTFILES-ml | 11 ++ src/guestfs.pod | 5 + v2v/Makefile.am | 185 +++++++++++++++++++++ v2v/README | 15 ++ v2v/cmdline.ml | 197 ++++++++++++++++++++++ v2v/convert_linux_common.ml | 131 +++++++++++++++ v2v/convert_linux_common.mli | 35 ++++ v2v/convert_linux_enterprise.ml | 185 +++++++++++++++++++++ v2v/convert_linux_enterprise.mli | 19 +++ v2v/convert_linux_grub.ml | 160 ++++++++++++++++++ v2v/convert_linux_grub.mli | 35 ++++ v2v/convert_windows.ml | 22 +++ v2v/convert_windows.mli | 19 +++ v2v/source_libvirt.ml | 118 ++++++++++++++ v2v/source_libvirt.mli | 27 +++ v2v/target_local.ml | 86 ++++++++++ v2v/target_local.mli | 21 +++ v2v/types.ml | 84 ++++++++++ v2v/types.mli | 77 +++++++++ v2v/utils-c.c | 43 +++++ v2v/utils.ml | 44 +++++ v2v/v2v.ml | 344 +++++++++++++++++++++++++++++++++++++++ v2v/virt-v2v.pod | 301 ++++++++++++++++++++++++++++++++++ v2v/xml-c.c | 240 +++++++++++++++++++++++++++ v2v/xml.ml | 50 ++++++ v2v/xml.mli | 57 +++++++ 31 files changed, 2525 insertions(+), 3 deletions(-) create mode 100644 v2v/Makefile.am create mode 100644 v2v/README create mode 100644 v2v/cmdline.ml create mode 100644 v2v/convert_linux_common.ml create mode 100644 v2v/convert_linux_common.mli create mode 100644 v2v/convert_linux_enterprise.ml create mode 100644 v2v/convert_linux_enterprise.mli create mode 100644 v2v/convert_linux_grub.ml create mode 100644 v2v/convert_linux_grub.mli create mode 100644 v2v/convert_windows.ml create mode 100644 v2v/convert_windows.mli create mode 100644 v2v/source_libvirt.ml create mode 100644 v2v/source_libvirt.mli create mode 100644 v2v/target_local.ml create mode 100644 v2v/target_local.mli create mode 100644 v2v/types.ml create mode 100644 v2v/types.mli create mode 100644 v2v/utils-c.c create mode 100644 v2v/utils.ml create mode 100644 v2v/v2v.ml create mode 100644 v2v/virt-v2v.pod create mode 100644 v2v/xml-c.c create mode 100644 v2v/xml.ml create mode 100644 v2v/xml.mli diff --git a/.gitignore b/.gitignore index dc8aaf8..9032597 100644 --- a/.gitignore +++ b/.gitignore @@ -256,6 +256,7 @@ Makefile.in /html/virt-tar.1.html /html/virt-tar-in.1.html /html/virt-tar-out.1.html +/html/virt-v2v.1.html /html/virt-win-reg.1.html /inspector/actual-*.xml /inspector/stamp-virt-inspector.pod @@ -526,3 +527,7 @@ Makefile.in /test-tool/libguestfs-test-tool-helper /test-tool/stamp-libguestfs-test-tool.pod /tools/virt-*.1 +/v2v/.depend +/v2v/stamp-virt-v2v.pod +/v2v/virt-v2v +/v2v/virt-v2v.1 diff --git a/Makefile.am b/Makefile.am index b135d65..3102e0b 100644 --- a/Makefile.am +++ b/Makefile.am @@ -132,7 +132,8 @@ SUBDIRS += \ builder builder/website \ resize \ sparsify \ - sysprep + sysprep \ + v2v endif # Perl tools. @@ -257,6 +258,7 @@ HTMLFILES = \ html/virt-tar.1.html \ html/virt-tar-in.1.html \ html/virt-tar-out.1.html \ + html/virt-v2v.1.html \ html/virt-win-reg.1.html HTMLSUPPORTFILES = \ @@ -319,7 +321,7 @@ all-local: grep -v -E '^python/utils.c$$' | \ LC_ALL=C sort > po/POTFILES cd $(srcdir); \ - find builder customize mllib resize sparsify sysprep -name '*.ml' | \ + find builder customize mllib resize sparsify sysprep v2v -name '*.ml' | \ LC_ALL=C sort > po/POTFILES-ml # Manual pages in top level directory. diff --git a/configure.ac b/configure.ac index 8abdc90..060e954 100644 --- a/configure.ac +++ b/configure.ac @@ -1710,7 +1710,8 @@ AC_CONFIG_FILES([Makefile tests/tmpdirs/Makefile tests/xfs/Makefile tests/xml/Makefile - tools/Makefile]) + tools/Makefile + v2v/Makefile]) AC_OUTPUT dnl Produce summary. diff --git a/fish/guestfish.pod b/fish/guestfish.pod index 25279fb..5cf6ebc 100644 --- a/fish/guestfish.pod +++ b/fish/guestfish.pod @@ -1624,6 +1624,7 @@ L<virt-sysprep(1)>, L<virt-tar(1)>, L<virt-tar-in(1)>, L<virt-tar-out(1)>, +L<virt-v2v(1)>, L<virt-win-reg(1)>, L<libguestfs-tools.conf(5)>, L<display(1)>, diff --git a/po/POTFILES b/po/POTFILES index 0fac8fe..b481157 100644 --- a/po/POTFILES +++ b/po/POTFILES @@ -318,3 +318,5 @@ src/test-utils.c src/tmpdirs.c src/utils.c test-tool/test-tool.c +v2v/utils-c.c +v2v/xml-c.c diff --git a/po/POTFILES-ml b/po/POTFILES-ml index 4dce0e5..b48a9de 100644 --- a/po/POTFILES-ml +++ b/po/POTFILES-ml @@ -80,3 +80,14 @@ sysprep/sysprep_operation_udev_persistent_net.ml sysprep/sysprep_operation_user_account.ml sysprep/sysprep_operation_utmp.ml sysprep/sysprep_operation_yum_uuid.ml +v2v/cmdline.ml +v2v/convert_linux_common.ml +v2v/convert_linux_enterprise.ml +v2v/convert_linux_grub.ml +v2v/convert_windows.ml +v2v/source_libvirt.ml +v2v/target_local.ml +v2v/types.ml +v2v/utils.ml +v2v/v2v.ml +v2v/xml.ml diff --git a/src/guestfs.pod b/src/guestfs.pod index 0f54625..f634442 100644 --- a/src/guestfs.pod +++ b/src/guestfs.pod @@ -4396,6 +4396,10 @@ created by another. Command line tools written in Perl (L<virt-win-reg(1)> and many others). +=item C<v2v> + +L<virt-v2v(1)> command and documentation. + =item C<csharp> =item C<erlang> @@ -4749,6 +4753,7 @@ L<virt-sysprep(1)>, L<virt-tar(1)>, L<virt-tar-in(1)>, L<virt-tar-out(1)>, +L<virt-v2v(1)>, L<virt-win-reg(1)>, L<guestfs-faq(1)>, L<guestfs-performance(1)>, diff --git a/v2v/Makefile.am b/v2v/Makefile.am new file mode 100644 index 0000000..707b15e --- /dev/null +++ b/v2v/Makefile.am @@ -0,0 +1,185 @@ +# libguestfs virt-v2v tool +# Copyright (C) 2009-2014 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) \ + virt-v2v.pod + +CLEANFILES = *~ *.cmi *.cmo *.cmx *.cmxa *.o virt-v2v + +# Alphabetical order. +SOURCES = \ + cmdline.ml \ + convert_linux_common.ml \ + convert_linux_common.mli \ + convert_linux_enterprise.ml \ + convert_linux_enterprise.mli \ + convert_linux_grub.ml \ + convert_linux_grub.mli \ + convert_windows.ml \ + convert_windows.mli \ + source_libvirt.ml \ + source_libvirt.mli \ + target_local.ml \ + target_local.mli \ + types.ml \ + types.mli \ + utils.ml \ + utils-c.c \ + v2v.ml \ + xml.ml \ + xml.mli \ + xml-c.c + +if HAVE_OCAML + +# Note this list must be in dependency order. +deps = \ + $(top_builddir)/fish/guestfish-progress.o \ + $(top_builddir)/mllib/tty-c.o \ + $(top_builddir)/mllib/progress-c.o \ + $(top_builddir)/mllib/common_gettext.cmx \ + $(top_builddir)/mllib/common_utils.cmx \ + $(top_builddir)/mllib/tTY.cmx \ + $(top_builddir)/mllib/progress.cmx \ + $(top_builddir)/mllib/config.cmx \ + types.cmx \ + utils-c.o \ + utils.cmx \ + xml-c.o \ + xml.cmx \ + source_libvirt.cmx \ + convert_linux_common.cmx \ + convert_linux_grub.cmx \ + convert_linux_enterprise.cmx \ + convert_windows.cmx \ + target_local.cmx \ + cmdline.cmx \ + v2v.cmx + +if HAVE_OCAMLOPT +OBJECTS = $(deps) +else +OBJECTS = $(patsubst %.cmx,%.cmo,$(deps)) +endif + +bin_SCRIPTS = virt-v2v + +# -I $(top_builddir)/src/.libs is a hack which forces corresponding -L +# option to be passed to gcc, so we don't try linking against an +# installed copy of libguestfs. +OCAMLPACKAGES = \ + -package str,unix \ + -I $(top_builddir)/src/.libs \ + -I ../gnulib/lib/.libs \ + -I $(top_builddir)/ocaml \ + -I $(top_builddir)/mllib +if HAVE_OCAML_PKG_GETTEXT +OCAMLPACKAGES += -package gettext-stub +endif + +OCAMLCFLAGS = -g -warn-error CDEFLMPSUVYZX $(OCAMLPACKAGES) +OCAMLOPTFLAGS = $(OCAMLCFLAGS) + +if HAVE_OCAMLOPT +virt-v2v: $(OBJECTS) + $(OCAMLFIND) ocamlopt $(OCAMLOPTFLAGS) \ + mlguestfs.cmxa -linkpkg $^ \ + -cclib '-lutils -lncurses $(LIBXML2_LIBS) -lgnu' \ + $(OCAML_GCOV_LDFLAGS) \ + -o $@ +else +virt-v2v: $(OBJECTS) + $(OCAMLFIND) ocamlc $(OCAMLCFLAGS) \ + mlguestfs.cma -linkpkg $^ \ + -cclib '-lutils -lncurses $(LIBXML2_LIBS) -lgnu' \ + -custom \ + $(OCAML_GCOV_LDFLAGS) \ + -o $@ +endif + +.mli.cmi: + $(OCAMLFIND) ocamlc $(OCAMLCFLAGS) -c $< -o $@ +.ml.cmo: + $(OCAMLFIND) ocamlc $(OCAMLCFLAGS) -c $< -o $@ +.ml.cmx: + $(OCAMLFIND) ocamlopt $(OCAMLOPTFLAGS) -c $< -o $@ + +# automake will decide we don't need C support in this file. Really +# we do, so we have to provide it ourselves. + +DEFAULT_INCLUDES = \ + -I. \ + -I$(top_builddir) \ + -I$(shell $(OCAMLC) -where) \ + -I$(top_srcdir)/src \ + -I$(top_srcdir)/fish \ + $(LIBXML2_CFLAGS) + +.c.o: + $(CC) $(CFLAGS) $(PROF_CFLAGS) $(DEFAULT_INCLUDES) -c $< -o $@ + +# Manual pages and HTML files for the website. + +man_MANS = virt-v2v.1 + +noinst_DATA = $(top_builddir)/html/virt-v2v.1.html + +virt-v2v.1 $(top_builddir)/html/virt-v2v.1.html: stamp-virt-v2v.pod + +stamp-virt-v2v.pod: virt-v2v.pod + $(PODWRAPPER) \ + --man virt-v2v.1 \ + --html $(top_builddir)/html/virt-v2v.1.html \ + --license GPLv2+ \ + $< + touch $@ + +CLEANFILES += stamp-virt-v2v.pod + +# Tests. + +TESTS_ENVIRONMENT = $(top_builddir)/run --test + +if ENABLE_APPLIANCE +TESTS +endif ENABLE_APPLIANCE + +check-valgrind: + $(MAKE) VG="$(top_builddir)/run @VG@" check + +# Dependencies. +depend: .depend + +.depend: $(wildcard $(abs_srcdir)/*.mli) $(wildcard $(abs_srcdir)/*.ml) + rm -f $@ $@-t + $(OCAMLFIND) ocamldep -I ../ocaml -I $(abs_srcdir) -I $(abs_top_builddir)/mllib $^ | \ + $(SED) 's/ *$$//' | \ + $(SED) -e :a -e '/ *\\$$/N; s/ *\\\n */ /; ta' | \ + $(SED) -e 's,$(abs_srcdir)/,$(builddir)/,g' | \ + sort > $@-t + mv $@-t $@ + +-include .depend + +endif + +DISTCLEANFILES = .depend + +.PHONY: depend docs diff --git a/v2v/README b/v2v/README new file mode 100644 index 0000000..259b211 --- /dev/null +++ b/v2v/README @@ -0,0 +1,15 @@ +Notes on the support matrix for upstream virt-v2v: + +All these in 32- and 64-bit variants. + +All these as Xen HV and PV variants, and ESX guests. + +RHEL 3, 4, 5, 6, 7. +RHEL 4.5, 4.8, 5.2, 5.4 - tested particularly because virtio was added + between these releases. + +Windows XP, 2000, 2003, 2008. + +SUSE: To do. + +VirtualBox: Not tested. diff --git a/v2v/cmdline.ml b/v2v/cmdline.ml new file mode 100644 index 0000000..966fe42 --- /dev/null +++ b/v2v/cmdline.ml @@ -0,0 +1,197 @@ +(* virt-v2v + * Copyright (C) 2009-2014 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. + *) + +(* Command line argument parsing. *) + +open Printf + +open Common_gettext.Gettext +open Common_utils + +open Types +open Utils + +let parse_cmdline () + let display_version () + printf "virt-v2v %s\n" Config.package_version; + exit 0 + in + + let debug_gc = ref false in + let input_conn = ref "" in + let output_conn = ref "" in + let output_format = ref "" in + let output_name = ref "" in + let output_storage = ref "" in + let machine_readable = ref false in + let quiet = ref false in + let verbose = ref false in + let trace = ref false in + + let input_mode = ref `Libvirt in + let set_input_mode = function + | "libvirt" -> input_mode := `Libvirt + | "libvirtxml" -> input_mode := `LibvirtXML + | s -> + error (f_"unknown -i option: %s") s + in + + let output_mode = ref `Libvirt in + let set_output_mode = function + | "libvirt" -> output_mode := `Libvirt + | "local" -> output_mode := `Local + | "ovirt" | "rhev" -> output_mode := `RHEV + | s -> + error (f_"unknown -o option: %s") s + in + + let output_alloc = ref `Sparse in + let set_output_alloc = function + | "sparse" -> output_alloc := `Sparse + | "preallocated" -> output_alloc := `Preallocated + | s -> + error (f_"unknown -oa option: %s") s + in + + let root_choice = ref `Ask in + let set_root_choice = function + | "ask" -> root_choice := `Ask + | "single" -> root_choice := `Single + | "first" -> root_choice := `First + | dev when string_prefix dev "/dev/" -> root_choice := `Dev dev + | s -> + error (f_"unknown --root option: %s") s + in + + let ditto = " -\"-" in + let argspec = Arg.align [ + "--debug-gc",Arg.Set debug_gc, " " ^ s_"Debug GC and memory allocations"; + "-i", Arg.String set_input_mode, "libvirtxml|libvirt " ^ s_"Set input mode (default: libvirt)"; + "-ic", Arg.Set_string input_conn, "uri " ^ s_"Libvirt URI"; + "--long-options", Arg.Unit display_long_options, " " ^ s_"List long options"; + "--machine-readable", Arg.Set machine_readable, " " ^ s_"Make output machine readable"; + "-o", Arg.String set_output_mode, "libvirt|local|rhev " ^ s_"Set output mode (default: libvirt)"; + "-oa", Arg.String set_output_alloc, "sparse|preallocated " ^ s_"Set output allocation mode"; + "-oc", Arg.Set_string output_conn, "uri " ^ s_"Libvirt URI"; + "-of", Arg.Set_string output_format, "raw|qcow2 " ^ s_"Set output format"; + "-on", Arg.Set_string output_name, "name " ^ s_"Rename guest when converting"; + "-os", Arg.Set_string output_storage, "storage " ^ s_"Set output storage location"; + "-q", Arg.Set quiet, " " ^ s_"Quiet output"; + "--quiet", Arg.Set quiet, ditto; + "--root", Arg.String set_root_choice,"ask|... " ^ s_"How to choose root filesystem"; + "-v", Arg.Set verbose, " " ^ s_"Enable debugging messages"; + "--verbose", Arg.Set verbose, ditto; + "-V", Arg.Unit display_version, " " ^ s_"Display version and exit"; + "--version", Arg.Unit display_version, ditto; + "-x", Arg.Set trace, " " ^ s_"Enable tracing of libguestfs calls"; + ] in + long_options := argspec; + let args = ref [] in + let anon_fun s = args := s :: !args in + let usage_msg + sprintf (f_"\ +%s: convert a guest to use KVM + + virt-v2v -ic esx://esx.example.com/ -os imported esx_guest + + virt-v2v -ic esx://esx.example.com/ \ + -o rhev -os rhev.nfs:/export_domain --network rhevm esx_guest + + virt-v2v -i libvirtxml -o local -os /tmp guest-domain.xml + +There is a companion front-end called \"virt-p2v\" which comes as an +ISO or CD image that can be booted on physical machines. + +A short summary of the options is given below. For detailed help please +read the man page virt-v2v(1). +") + prog in + Arg.parse argspec anon_fun usage_msg; + + (* Dereference the arguments. *) + let args = List.rev !args in + let debug_gc = !debug_gc in + let input_conn = match !input_conn with "" -> None | s -> Some s in + let input_mode = !input_mode in + let machine_readable = !machine_readable in + let output_alloc = !output_alloc in + let output_conn = match !output_conn with "" -> None | s -> Some s in + let output_format = match !output_format with "" -> None | s -> Some s in + let output_mode = !output_mode in + let output_name = match !output_name with "" -> None | s -> Some s in + let output_storage = !output_storage in + let quiet = !quiet in + let root_choice = !root_choice in + let verbose = !verbose in + let trace = !trace in + + (* No arguments and machine-readable mode? Print out some facts + * about what this binary supports. + *) + if args = [] && machine_readable then ( + printf "virt-v2v\n"; + printf "libguestfs-rewrite\n"; + exit 0 + ); + + (* Parsing of the argument(s) depends on the input mode. *) + let input + match input_mode with + | `Libvirt -> + (* -i libvirt: Expecting a single argument which is the name + * of the libvirt guest. + *) + let guest + match args with + | [guest] -> guest + | _ -> + error (f_"expecting a libvirt guest name on the command line") in + InputLibvirt (input_conn, guest) + | `LibvirtXML -> + (* -i libvirtxml: Expecting a filename (XML file). *) + let filename + match args with + | [filename] -> filename + | _ -> + error (f_"expecting a libvirt XML file name on the command line") in + InputLibvirtXML filename in + + (* Parse the output mode. *) + let output + match output_mode with + | `Libvirt -> + if output_storage <> "" then + error (f_"-o libvirt: do not use the -os option"); + OutputLibvirt output_conn + | `Local -> + if output_storage = "" then + error (f_"-o local: output directory was not specified, use '-os /dir'"); + let dir_exists + try Sys.is_directory output_storage with Sys_error _ -> false in + if not dir_exists then + error (f_"-os %s: output directory does not exist or is not a directory") + output_storage; + OutputLocal output_storage + | `RHEV -> + if output_storage = "" then + error (f_"-o local: output storage was not specified, use '-os'"); + OutputRHEV output_storage in + + input, output, + debug_gc, output_alloc, output_format, output_name, + quiet, root_choice, trace, verbose diff --git a/v2v/convert_linux_common.ml b/v2v/convert_linux_common.ml new file mode 100644 index 0000000..7c6e811 --- /dev/null +++ b/v2v/convert_linux_common.ml @@ -0,0 +1,131 @@ +(* virt-v2v + * Copyright (C) 2009-2014 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 Common_gettext.Gettext +open Common_utils + +open Types +open Utils + +module StringMap = Map.Make (String) +let keys map = StringMap.fold (fun k _ ks -> k :: ks) map [] + +(* Wrappers around aug_init & aug_load which can dump out full Augeas + * parsing problems when debugging is enabled. + *) +let rec augeas_init verbose g + g#aug_init "/" 1; + if verbose then augeas_debug_errors g + +and augeas_reload verbose g + g#aug_load (); + if verbose then augeas_debug_errors g + +and augeas_debug_errors g + try + let errors = g#aug_match "/augeas/files//error" in + let errors = Array.to_list errors in + let map + List.fold_left ( + fun map error -> + let detail_paths = g#aug_match (error ^ "//*") in + let detail_paths = Array.to_list detail_paths in + List.fold_left ( + fun map path -> + (* path is "/augeas/files/<filename>/error/<field>". Put + * <filename>, <field> and the value of this Augeas field + * into a map. + *) + let i = string_find path "/error/" in + assert (i >= 0); + let filename = String.sub path 13 (i-13) in + let field = String.sub path (i+7) (String.length path - (i+7)) in + + let detail = g#aug_get path in + + let fmap : string StringMap.t + try StringMap.find filename map + with Not_found -> StringMap.empty in + let fmap = StringMap.add field detail fmap in + StringMap.add filename fmap map + ) map detail_paths + ) StringMap.empty errors in + + let filenames = keys map in + let filenames = List.sort compare filenames in + + List.iter ( + fun filename -> + printf "augeas failed to parse %s:\n" filename; + let fmap = StringMap.find filename map in + (try + let msg = StringMap.find "message" fmap in + printf " error \"%s\"" msg + with Not_found -> () + ); + (try + let line = StringMap.find "line" fmap + and char = StringMap.find "char" fmap in + printf " at line %s char %s" line char + with Not_found -> () + ); + (try + let lens = StringMap.find "lens" fmap in + printf " in lens %s" lens + with Not_found -> () + ); + printf "\n" + ) filenames; + + flush stdout + with + G.Error msg -> eprintf "%s: augeas: %s (ignored)\n" prog msg + +let install verbose g inspect packages + assert false + +let remove verbose g inspect packages + if packages <> [] then ( + let root = inspect.i_root in + let package_format = g#inspect_get_package_format root in + match package_format with + | "rpm" -> + let cmd = [ "rpm"; "-e" ] @ packages in + let cmd = Array.of_list cmd in + ignore (g#command cmd); + + (* Reload Augeas in case anything changed. *) + augeas_reload verbose g + + | format -> + error (f_"don't know how to remove packages using %s: packages: %s") + format (String.concat " " packages) + ) + +let file_owned verbose g inspect file + let root = inspect.i_root in + let package_format = g#inspect_get_package_format root in + match package_format with + | "rpm" -> + let cmd = [| "rpm"; "-qf"; file |] in + (try ignore (g#command cmd); true with G.Error _ -> false) + + | format -> + error (f_"don't know how to find package owner using %s") format diff --git a/v2v/convert_linux_common.mli b/v2v/convert_linux_common.mli new file mode 100644 index 0000000..5a6a440 --- /dev/null +++ b/v2v/convert_linux_common.mli @@ -0,0 +1,35 @@ +(* virt-v2v + * Copyright (C) 2009-2014 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. + *) + +(** Common Linux conversion code. *) + +val augeas_init : bool -> Guestfs.guestfs -> unit +val augeas_reload : bool -> Guestfs.guestfs -> unit +(** Wrappers around [g#aug_init] and [g#aug_load], which (if verbose) + provide additional debugging information about parsing problems + that augeas found. *) + +val install : bool -> Guestfs.guestfs -> Types.inspect -> string list -> unit +(** Install package(s) from the list in the guest (or ensure they are + installed. *) + +val remove : bool -> Guestfs.guestfs -> Types.inspect -> string list -> unit +(** Uninstall package(s). *) + +val file_owned : bool -> Guestfs.guestfs -> Types.inspect -> string -> bool +(** Returns true if the file is owned by an installed package. *) diff --git a/v2v/convert_linux_enterprise.ml b/v2v/convert_linux_enterprise.ml new file mode 100644 index 0000000..8ec3ac3 --- /dev/null +++ b/v2v/convert_linux_enterprise.ml @@ -0,0 +1,185 @@ +(* virt-v2v + * Copyright (C) 2009-2014 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. + *) + +(* Convert various RPM-based Linux enterprise distros. This module + * handles: + * + * - RHEL and derivatives like CentOS and ScientificLinux + * - SUSE + * - OpenSUSE and Fedora (not enterprisey, but similar enough to RHEL/SUSE) + *) + +open Printf + +open Common_gettext.Gettext +open Common_utils + +open Utils +open Types + +let rec convert verbose (g : Guestfs.guestfs) inspect + let root = inspect.i_root in + let apps = inspect.i_apps + and typ = g#inspect_get_type root + and distro = g#inspect_get_distro root in + + let (*is_rhel_family + typ = "linux" && + (distro = "rhel" || distro = "centos" + || distro = "scientificlinux" || distro = "redhat-based") + + and*) is_suse_family + typ = "linux" && + (distro = "sles" || distro = "suse-based" || distro = "opensuse") in + + let clean_rpmdb () + (* Clean RPM database. *) + assert (g#inspect_get_package_format root = "rpm"); + let dbfiles = g#glob_expand "/var/lib/rpm/__db.00?" in + let dbfiles = Array.to_list dbfiles in + List.iter g#rm_f dbfiles + + and autorelabel () + (* Only do autorelabel if load_policy binary exists. Actually + * loading the policy is problematic. + *) + if g#is_file ~followsymlinks:true "/usr/sbin/load_policy" then + g#touch "/.autorelabel"; + + and get_grub () + (* Detect if grub2 or grub1 is installed by trying to create + * an object of each sort. + *) + try Convert_linux_grub.grub2 verbose g inspect + with Failure grub2_error -> + try Convert_linux_grub.grub1 verbose g inspect + with Failure grub1_error -> + error (f_"no grub configuration found in this guest. +Grub2 error was: %s +Grub1/grub-legacy error was: %s") + grub2_error grub1_error + + and unconfigure_xen () + (* Remove kmod-xenpv-* (RHEL 3). *) + let xenmods + filter_map ( + fun { G.app2_name = name } -> + if name = "kmod-xenpv" || string_prefix name "kmod-xenpv-" then + Some name + else + None + ) apps in + Convert_linux_common.remove verbose g inspect xenmods; + + (* Undo related nastiness if kmod-xenpv was installed. *) + if xenmods <> [] then ( + (* kmod-xenpv modules may have been manually copied to other kernels. + * Hunt them down and destroy them. + *) + let dirs = g#find "/lib/modules" in + let dirs = Array.to_list dirs in + let dirs = List.filter (fun s -> string_find s "/xenpv" >= 0) dirs in + let dirs = List.map ((^) "/lib/modules/") dirs in + let dirs = List.filter g#is_dir dirs in + + (* Check it's not owned by an installed application. *) + let dirs = List.filter ( + fun d -> not (Convert_linux_common.file_owned verbose g inspect d) + ) dirs in + + (* Remove any unowned xenpv directories. *) + List.iter g#rm_rf dirs; + + (* rc.local may contain an insmod or modprobe of the xen-vbd driver, + * added by an installation script. + *) + (try + let lines = g#read_lines "/etc/rc.local" in + let lines = Array.to_list lines in + let rex = Str.regexp "\\b\\(insmod|modprobe\\)\b.*\\bxen-vbd" in + let lines = List.map ( + fun s -> + if Str.string_match rex s 0 then + "#" ^ s + else + s + ) lines in + let file = String.concat "\n" lines ^ "\n" in + g#write "/etc/rc.local" file + with + G.Error msg -> eprintf "%s: /etc/rc.local: %s (ignored)\n" prog msg + ); + ); + + if is_suse_family then ( + (* Remove xen modules from INITRD_MODULES and DOMU_INITRD_MODULES. *) + let variables = ["INITRD_MODULES"; "DOMU_INITRD_MODULES"] in + let xen_modules = ["xennet"; "xen-vnif"; "xenblk"; "xen-vbd"] in + let modified = ref false in + List.iter ( + fun var -> + List.iter ( + fun xen_mod -> + let expr + sprintf "/file/etc/sysconfig/kernel/%s/value[. = '%s']" + var xen_mod in + let entries = g#aug_match expr in + let entries = Array.to_list entries in + if entries <> [] then ( + List.iter (fun e -> ignore (g#aug_rm e)) entries; + modified := true + ) + ) xen_modules + ) variables; + if !modified then g#aug_save () + ); + + and unconfigure_vbox () + () + + and unconfigure_vmware () + () + + and unconfigure_citrix () + () + + in + + clean_rpmdb (); + autorelabel (); + Convert_linux_common.augeas_init verbose g; + let grub = get_grub () in + ignore grub (* XXX *); + unconfigure_xen (); + unconfigure_vbox (); + unconfigure_vmware (); + unconfigure_citrix (); + + + + + + + + let guestcaps = { + gcaps_block_bus = "virtio" (* XXX *); + gcaps_net_bus = "virtio" (* XXX *); + (* XXX display *) + } in + + guestcaps diff --git a/v2v/convert_linux_enterprise.mli b/v2v/convert_linux_enterprise.mli new file mode 100644 index 0000000..d1e60fe --- /dev/null +++ b/v2v/convert_linux_enterprise.mli @@ -0,0 +1,19 @@ +(* virt-v2v + * Copyright (C) 2009-2014 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. + *) + +val convert : bool -> Guestfs.guestfs -> Types.inspect -> Types.guestcaps diff --git a/v2v/convert_linux_grub.ml b/v2v/convert_linux_grub.ml new file mode 100644 index 0000000..2de8b5c --- /dev/null +++ b/v2v/convert_linux_grub.ml @@ -0,0 +1,160 @@ +(* virt-v2v + * Copyright (C) 2009-2014 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. + *) + +module G = Guestfs + +open Printf + +open Common_gettext.Gettext + +open Types + +type t = { + config_file : string; (* grub configuration file *) + g : Guestfs.guestfs; (* libguestfs handle *) + inspect : inspect; (* inspection data *) + + (* Variant stuff for each version of grub. *) + grub : grub_variant; +} +and grub_variant = Grub1 of grub1_data | Grub2 +and grub1_data = { + grub_fs : string; (* grub filesystem prefix *) +} + +(* Create a grub1 object. *) +let rec grub1 verbose (g : Guestfs.guestfs) inspect + let root = inspect.i_root in + + (* Look for a grub configuration file. *) + let config_file + try + List.find ( + fun file -> g#is_file ~followsymlinks:true file + ) ["/boot/grub/menu.lst"; "/boot/grub/grub.conf"] + with + Not_found -> + failwith (s_"no grub/grub1/grub-legacy configuration file was found") in + + (* Check for EFI and convert if found. *) + (try let dev = check_efi g in grub1_convert_from_efi verbose g dev + with Not_found -> () + ); + + (* Find the path that has to be prepended to filenames in grub.conf + * in order to make them absolute. + *) + let grub_fs + let mounts = g#inspect_get_mountpoints root in + try + List.find ( + fun path -> List.mem_assoc path mounts + ) [ "/boot/grub"; "/boot" ] + with Not_found -> "" in + + (* Ensure Augeas is reading the grub configuration file, and if not + * then add it. + *) + let () + let incls = g#aug_match "/augeas/load/Grub/incl" in + let incls = Array.to_list incls in + let incls_contains_conf + List.exists (fun incl -> g#aug_get incl = config_file) incls in + if not incls_contains_conf then ( + g#aug_set "/augeas/load/Grub/incl[last()+1]" config_file; + Convert_linux_common.augeas_reload verbose g; + ) in + + { config_file = config_file; g = g; inspect = inspect; + grub = Grub1 { grub_fs = grub_fs } } + +(* Reinstall grub. *) +and grub1_convert_from_efi verbose g dev + g#cp "/etc/grub.conf" "/boot/grub/grub.conf"; + g#ln_sf "/boot/grub/grub.conf" "/etc/grub.conf"; + + (* Reload Augeas to pick up new location of grub.conf. *) + Convert_linux_common.augeas_reload verbose g; + + ignore (g#command [| "grub-install"; dev |]) + +and grub2 verbose (g : Guestfs.guestfs) inspect + (* Look for a grub2 configuration file. *) + let config_file = "/boot/grub2/grub.cfg" in + if not (g#is_file ~followsymlinks:true config_file) then ( + let msg + sprintf (f_"no grub2 configuration file was found (expecting %s)") + config_file in + failwith msg + ); + + (* Check for EFI and convert if found. *) + (try + let dev = check_efi g in + grub2_convert_from_efi verbose g inspect dev + with Not_found -> () + ); + + { config_file = config_file; g = g; inspect = inspect; + grub = Grub2 } + +(* For grub2: + * - Turn the EFI partition into a BIOS Boot Partition + * - Remove the former EFI partition from fstab + * - Install the non-EFI version of grub + * - Install grub2 in the BIOS Boot Partition + * - Regenerate grub.cfg + *) +and grub2_convert_from_efi verbose g inspect dev + (* EFI systems boot using grub2-efi, and probably don't have the + * base grub2 package installed. + *) + Convert_linux_common.install verbose g inspect ["grub2"]; + + (* Relabel the EFI boot partition as a BIOS boot partition. *) + g#part_set_gpt_type dev 1 "21686148-6449-6E6F-744E-656564454649"; + + (* Delete the fstab entry for the EFI boot partition. *) + let nodes = g#aug_match "/files/etc/fstab/*[file = '/boot/efi']" in + let nodes = Array.to_list nodes in + List.iter (fun node -> ignore (g#aug_rm node)) nodes; + g#aug_save (); + + (* Install grub2 in the BIOS boot partition. This overwrites the + * previous contents of the EFI boot partition. + *) + ignore (g#command [| "grub2-install"; dev |]); + + (* Re-generate the grub2 config, and put it in the correct place *) + ignore (g#command [| "grub2-mkconfig"; "-o"; "/boot/grub2/grub.cfg" |]) + +and check_efi g + if Array.length (g#glob_expand "/boot/efi/EFI/*/grub.cfg") < 1 then + raise Not_found; + + (* Check the first partition of each device looking for an EFI + * boot partition. We can't be sure which device is the boot + * device, so we just check them all. + *) + let devs = g#list_devices () in + let devs = Array.to_list devs in + List.find ( + fun dev -> + try g#part_get_gpt_type dev 1 = "C12A7328-F81F-11D2-BA4B-00A0C93EC93B" + with G.Error _ -> false + ) devs diff --git a/v2v/convert_linux_grub.mli b/v2v/convert_linux_grub.mli new file mode 100644 index 0000000..54f8304 --- /dev/null +++ b/v2v/convert_linux_grub.mli @@ -0,0 +1,35 @@ +(* virt-v2v + * Copyright (C) 2009-2014 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. + *) + +(** Common code handling grub1 (grub-legacy) and grub2 operations. *) + +type t + +val grub1 : bool -> Guestfs.guestfs -> Types.inspect -> t +(** Detect if grub1/grub-legacy is used by this guest and return a + grub object if so. + + This raises [Failure] if grub1 is not used by this guest or some + other problem happens. *) + +val grub2 : bool -> Guestfs.guestfs -> Types.inspect -> t +(** Detect if grub2 is used by this guest and return a grub object + if so. + + This raises [Failure] if grub2 is not used by this guest or some + other problem happens. *) diff --git a/v2v/convert_windows.ml b/v2v/convert_windows.ml new file mode 100644 index 0000000..e5d1ea8 --- /dev/null +++ b/v2v/convert_windows.ml @@ -0,0 +1,22 @@ +(* virt-v2v + * Copyright (C) 2009-2014 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. + *) + +(* Convert Windows guests. *) + +let convert verbose g inspect + assert false diff --git a/v2v/convert_windows.mli b/v2v/convert_windows.mli new file mode 100644 index 0000000..d1e60fe --- /dev/null +++ b/v2v/convert_windows.mli @@ -0,0 +1,19 @@ +(* virt-v2v + * Copyright (C) 2009-2014 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. + *) + +val convert : bool -> Guestfs.guestfs -> Types.inspect -> Types.guestcaps diff --git a/v2v/source_libvirt.ml b/v2v/source_libvirt.ml new file mode 100644 index 0000000..bdea8d4 --- /dev/null +++ b/v2v/source_libvirt.ml @@ -0,0 +1,118 @@ +(* virt-v2v + * Copyright (C) 2009-2014 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 Common_gettext.Gettext +open Common_utils + +open Types +open Utils + +let create_xml xml + let doc = Xml.parse_memory xml in + let xpathctx = Xml.xpath_new_context doc in + + let xpath_to_string expr default + let obj = Xml.xpath_eval_expression xpathctx expr in + if Xml.xpathobj_nr_nodes obj < 1 then default + else ( + let node = Xml.xpathobj_node doc obj 0 in + Xml.node_as_string node + ) in + let xpath_to_int expr default + let obj = Xml.xpath_eval_expression xpathctx expr in + if Xml.xpathobj_nr_nodes obj < 1 then default + else ( + let node = Xml.xpathobj_node doc obj 0 in + let str = Xml.node_as_string node in + try int_of_string str + with Failure "int_of_string" -> + error (f_"expecting XML expression to return an integer (expression: %s)") + expr + ) in + + let dom_type = xpath_to_string "/domain/@type" "" in + let name = xpath_to_string "/domain/name/text()" "" in + let memory = xpath_to_int "/domain/memory/text()" 0 in + let memory = Int64.of_int memory *^ 1024L in + let vcpu = xpath_to_int "/domain/vcpu/text()" 0 in + let arch = xpath_to_string "/domain/os/type/@arch" "" in + + let features + let features = ref [] in + let obj = Xml.xpath_eval_expression xpathctx "/domain/features/*" in + let nr_nodes = Xml.xpathobj_nr_nodes obj in + for i = 0 to nr_nodes-1 do + let node = Xml.xpathobj_node doc obj i in + features := Xml.node_name node :: !features + done; + !features in + + (* Non-removable disk devices. *) + let disks + let disks = ref [] in + let obj + Xml.xpath_eval_expression xpathctx + "/domain/devices/disk[@device='disk']" in + let nr_nodes = Xml.xpathobj_nr_nodes obj in + if nr_nodes < 1 then + error (f_"this guest has no non-removable disks"); + for i = 0 to nr_nodes-1 do + let node = Xml.xpathobj_node doc obj i in + Xml.xpathctx_set_current_context xpathctx node; + let path = xpath_to_string "source/@file | source/@dev" "" in + if path <> "" then ( + let format + let format = xpath_to_string "driver/@type" "" in + if format <> "" then Some format else None in + disks := (path, format) :: !disks + ) + done; + List.rev !disks in + + (* XXX Much more metadata needs to be collected here: + * - graphics + * - cdroms + * - floppies + * - network interfaces + * See: lib/Sys/VirtConvert/Connection/LibVirt.pm + *) + + { + s_dom_type = dom_type; + s_name = name; + s_memory = memory; + s_vcpu = vcpu; + s_arch = arch; + s_features = features; + s_disks = disks; + } + +let create_from_xml file + let xml = read_whole_file file in + create_xml xml + +let create libvirt_uri guest + let cmd + match libvirt_uri with + | None -> sprintf "virsh dumpxml %s" (quote guest) + | Some uri -> sprintf "virsh -c %s dumpxml %s" (quote uri) (quote guest) in + let lines = external_command ~prog cmd in + let xml = String.concat "\n" lines in + create_xml xml diff --git a/v2v/source_libvirt.mli b/v2v/source_libvirt.mli new file mode 100644 index 0000000..1e3b1e1 --- /dev/null +++ b/v2v/source_libvirt.mli @@ -0,0 +1,27 @@ +(* virt-v2v + * Copyright (C) 2009-2014 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. + *) + +(** [-i libvirt] and [-i libvirtxml] sources. *) + +val create : string option -> string -> Types.source +(** [create libvirt_uri guest] reads the source metadata from the + named libvirt guest. *) + +val create_from_xml : string -> Types.source +(** [create_from_xml filename] reads the source metadata from the + libvirt XML file. *) diff --git a/v2v/target_local.ml b/v2v/target_local.ml new file mode 100644 index 0000000..ed4e5e3 --- /dev/null +++ b/v2v/target_local.ml @@ -0,0 +1,86 @@ +(* virt-v2v + * Copyright (C) 2009-2014 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 Common_gettext.Gettext +open Common_utils + +open Types +open Utils + +let initialize dir overlays + List.map ( + fun ov -> + let target_file = dir // "disk-" ^ ov.ov_sd in + { ov with ov_target_file = target_file; ov_target_file_tmp = target_file } + ) overlays + +let create_metadata dir source overlays guestcaps + let name = source.s_name in + let file = dir // name ^ ".xml" in + + let chan = open_out file in + let p fs = fprintf chan fs in + + p "<domain type='%s'>\n" "kvm"; (* Always assume target is kvm? *) + p " <name>%s</name>\n" name; + let memory_k = source.s_memory /^ 1024L in + p " <memory unit='KiB'>%Ld</memory>\n" memory_k; + p " <currentMemory unit='KiB'>%Ld</currentMemory>\n" memory_k; + p " <vcpu>%d</vcpu>\n" source.s_vcpu; + p " <os>\n"; + p " <type arch='%s'>hvm</type>\n" source.s_arch; + p " </os>\n"; + p " <features>\n"; + List.iter (p " <%s/>\n") source.s_features; + p " </features>\n"; + + p " <on_poweroff>destroy</on_poweroff>\n"; + p " <on_reboot>restart</on_reboot>\n"; + p " <on_crash>restart</on_crash>\n"; + p " <devices>\n"; + + let block_prefix + if guestcaps.gcaps_block_bus = "virtio" then "vd" else "hd" in + iteri ( + fun i ov -> + p " <disk type='file' device='disk'>\n"; + p " <driver name='qemu' type='%s' cache='none'/>\n" + ov.ov_target_format; + p " <source file='%s'/>\n" (xml_quote_attr ov.ov_target_file); + p " <target dev='%s%s' bus='%s'/>\n" + block_prefix (drive_name i) guestcaps.gcaps_block_bus; + p " </disk>\n"; + ) overlays; + + p " <input type='tablet' bus='usb'/>\n"; + p " <input type='mouse' bus='ps2'/>\n"; + p " <console type='pty'/>\n"; + + (* XXX Missing here from old virt-v2v: + <video/> + <graphics/> + cdroms and floppies + network interfaces + See: lib/Sys/VirtConvert/Connection/LibVirtTarget.pm + *) + + p "</domain>\n"; + + close_out chan diff --git a/v2v/target_local.mli b/v2v/target_local.mli new file mode 100644 index 0000000..1833ecb --- /dev/null +++ b/v2v/target_local.mli @@ -0,0 +1,21 @@ +(* virt-v2v + * Copyright (C) 2009-2014 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. + *) + +val initialize : string -> Types.overlay list -> Types.overlay list + +val create_metadata : string -> Types.source -> Types.overlay list -> Types.guestcaps -> unit diff --git a/v2v/types.ml b/v2v/types.ml new file mode 100644 index 0000000..0f5ae86 --- /dev/null +++ b/v2v/types.ml @@ -0,0 +1,84 @@ +(* virt-v2v + * Copyright (C) 2009-2014 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 + +(* Types. See types.mli for documentation. *) + +type input +| InputLibvirt of string option * string +| InputLibvirtXML of string + +type output +| OutputLibvirt of string option +| OutputLocal of string +| OutputRHEV of string + +type source = { + s_dom_type : string; + s_name : string; + s_memory : int64; + s_vcpu : int; + s_arch : string; + s_features : string list; + s_disks : source_disk list; +} +and source_disk = string * string option + +type overlay = { + ov_overlay : string; + ov_target_file : string; + ov_target_file_tmp : string; + ov_target_format : string; + ov_sd : string; + ov_virtual_size : int64; + ov_preallocation : string option; + ov_source_file : string; + ov_source_format : string option; +} + +let string_of_overlay ov + sprintf "\ +ov_overlay = %s +ov_target_file = %s +ov_target_file_tmp = %s +ov_target_format = %s +ov_sd = %s +ov_virtual_size = %Ld +ov_preallocation = %s +ov_source_file = %s +ov_source_format = %s +" + ov.ov_overlay + ov.ov_target_file ov.ov_target_file_tmp + ov.ov_target_format + ov.ov_sd + ov.ov_virtual_size + (match ov.ov_preallocation with None -> "None" | Some s -> s) + ov.ov_source_file + (match ov.ov_source_format with None -> "None" | Some s -> s) + +type inspect = { + i_root : string; + i_apps : Guestfs.application2 list; +} + +type guestcaps = { + gcaps_block_bus : string; + gcaps_net_bus : string; +} diff --git a/v2v/types.mli b/v2v/types.mli new file mode 100644 index 0000000..e7e72e0 --- /dev/null +++ b/v2v/types.mli @@ -0,0 +1,77 @@ +(* virt-v2v + * Copyright (C) 2009-2014 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. + *) + +(** Types. *) + +type input +| InputLibvirt of string option * string (* -i libvirt: -ic + guest name *) +| InputLibvirtXML of string (* -i libvirtxml: XML file name *) +(** The input arguments as specified on the command line. *) + +type output +| OutputLibvirt of string option (* -o libvirt: -oc *) +| OutputLocal of string (* -o local: directory *) +| OutputRHEV of string (* -o rhev: output storage *) +(** The output arguments as specified on the command line. *) + +type source = { + s_dom_type : string; (** Source domain type, eg "kvm" *) + s_name : string; (** Guest name. *) + s_memory : int64; (** Memory size (bytes). *) + s_vcpu : int; (** Number of CPUs. *) + s_arch : string; (** Architecture. *) + s_features : string list; (** Machine features. *) + s_disks : source_disk list; (** Disk images. *) +} +(** The source: metadata, disk images. *) + +and source_disk = string * string option +(** A source file is a qemu URI and a format. *) + +type overlay = { + ov_overlay : string; (** Local overlay file (qcow2 format). *) + ov_target_file : string; (** Destination file (real). *) + ov_target_file_tmp : string; (** Destination file (temporary). *) + ov_target_format : string; (** Destination format (eg. -of option). *) + ov_sd : string; (** sdX libguestfs name of disk. *) + ov_virtual_size : int64; (** Virtual disk size in bytes. *) + ov_preallocation : string option; (** ?preallocation option. *) + + (* Note: the next two fields are for information only and must not + * be opened/copied/etc. + *) + ov_source_file : string; (** qemu URI for source file. *) + ov_source_format : string option; (** Source file format, if known. *) +} +(** Disk overlays and destination disks. *) + +val string_of_overlay : overlay -> string + +type inspect = { + i_root : string; (** Root device. *) + i_apps : Guestfs.application2 list; (** Packages installed. *) +} +(** Inspection information. Only the applications list is stored here + as that is the only one which is slow/inconvenient to fetch. *) + +type guestcaps = { + gcaps_block_bus : string; (** "virtio", "ide", possibly others *) + gcaps_net_bus : string; (** "virtio", "e1000", possibly others *) + (* XXX acpi, display *) +} +(** Guest capabilities after conversion. eg. Was virtio found or installed? *) diff --git a/v2v/utils-c.c b/v2v/utils-c.c new file mode 100644 index 0000000..f6a5d74 --- /dev/null +++ b/v2v/utils-c.c @@ -0,0 +1,43 @@ +/* virt-v2v + * Copyright (C) 2009-2014 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 <config.h> + +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> + +#include <caml/alloc.h> +#include <caml/memory.h> +#include <caml/mlvalues.h> + +#include "guestfs.h" +#include "guestfs-internal-frontend.h" + +value +v2v_utils_drive_name (value indexv) +{ + CAMLparam1 (indexv); + CAMLlocal1 (namev); + char name[64]; + + guestfs___drive_name (Int_val (indexv), name); + namev = caml_copy_string (name); + + CAMLreturn (namev); +} diff --git a/v2v/utils.ml b/v2v/utils.ml new file mode 100644 index 0000000..6155f9a --- /dev/null +++ b/v2v/utils.ml @@ -0,0 +1,44 @@ +(* virt-v2v + * Copyright (C) 2009-2014 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. + *) + +(* Utilities used in virt-v2v only. *) + +open Printf + +open Common_gettext.Gettext +open Common_utils + +open Types + +let prog = Filename.basename Sys.executable_name +let error ?exit_code fs = error ~prog ?exit_code fs + +let quote = Filename.quote + +(* Quote XML <element attr='...'> content. Note you must use single + * quotes around the attribute. + *) +let xml_quote_attr str + let str = Common_utils.replace_str str "&" "&" in + let str = Common_utils.replace_str str "'" "'" in + let str = Common_utils.replace_str str "<" "<" in + let str = Common_utils.replace_str str ">" ">" in + str + +external drive_name : int -> string = "v2v_utils_drive_name" + diff --git a/v2v/v2v.ml b/v2v/v2v.ml new file mode 100644 index 0000000..4b62e05 --- /dev/null +++ b/v2v/v2v.ml @@ -0,0 +1,344 @@ +(* virt-v2v + * Copyright (C) 2009-2014 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 Unix +open Printf + +open Common_gettext.Gettext + +module G = Guestfs + +open Common_utils +open Types +open Utils + +let () = Random.self_init () + +let rec main () + (* Handle the command line. *) + let input, output, + debug_gc, output_alloc, output_format, output_name, + quiet, root_choice, trace, verbose + Cmdline.parse_cmdline () in + + let msg fs = make_message_function ~quiet fs in + + let source + match input with + | InputLibvirt (libvirt_uri, guest) -> + Source_libvirt.create libvirt_uri guest + | InputLibvirtXML filename -> + Source_libvirt.create_from_xml filename in + + (* Create a qcow2 v3 overlay to protect the source image(s). There + * is a specific reason to use the newer qcow2 variant: Because the + * L2 table can store zero clusters efficiently, and because + * discarded blocks are stored as zero clusters, this should allow us + * to fstrim/blkdiscard and avoid copying significant parts of the + * data over the wire. + *) + msg (f_"Creating an overlay to protect the source from being modified"); + let overlays + List.map ( + fun (qemu_uri, format) -> + let overlay = Filename.temp_file "v2vovl" ".qcow2" in + unlink_on_exit overlay; + + let options + "compat=1.1,lazy_refcounts=on" ^ + (match format with None -> "" + | Some fmt -> ",backing_fmt=" ^ fmt) in + let cmd + sprintf "qemu-img create -q -f qcow2 -b %s -o %s %s" + (quote qemu_uri) (quote options) overlay in + if Sys.command cmd <> 0 then + error (f_"qemu-img command failed, see earlier errors"); + overlay, qemu_uri, format + ) source.s_disks in + + (* Open the guestfs handle. *) + msg (f_"Opening the overlay"); + let g = new G.guestfs () in + g#set_trace trace; + g#set_verbose verbose; + List.iter ( + fun (overlay, _, _) -> + g#add_drive_opts overlay + ~format:"qcow2" ~cachemode:"unsafe" ~discard:"besteffort" + ) overlays; + + g#launch (); + + (* Work out where we will write the final output. Do this early + * just so we can display errors to the user before doing too much + * work. + *) + let overlays + initialize_target g output output_alloc output_format overlays in + + (* Inspection - this also mounts up the filesystems. *) + msg (f_"Inspecting the overlay"); + let inspect = inspect_source g root_choice in + + (* Conversion. *) + let guestcaps + let root = inspect.i_root in + + (match g#inspect_get_product_name root with + | "unknown" -> + msg (f_"Converting the guest to run on KVM") + | prod -> + msg (f_"Converting %s to run on KVM") prod + ); + + match g#inspect_get_type root with + | "linux" -> + (match g#inspect_get_distro root with + | "fedora" + | "rhel" | "centos" | "scientificlinux" | "redhat-based" + | "sles" | "suse-based" | "opensuse" -> + Convert_linux_enterprise.convert verbose g inspect + + | distro -> + error (f_"virt-v2v is unable to convert this guest type (linux/distro=%s)") distro + ); + + | "windows" -> Convert_windows.convert verbose g inspect + + | typ -> + error (f_"virt-v2v is unable to convert this guest type (type=%s)") typ in + + (* Trim the filesystems to reduce transfer size. *) + msg (f_"Trimming filesystems to reduce amount of data to copy"); + let () + let mps = g#mountpoints () in + List.iter ( + fun (_, mp) -> + try g#fstrim mp + with G.Error msg -> eprintf "%s: %s (ignored)\n" mp msg + ) mps in + + msg (f_"Closing the overlay"); + g#umount_all (); + g#shutdown (); + g#close (); + + (* Copy the source to the output. *) + let delete_target_on_exit = ref true in + at_exit (fun () -> + if !delete_target_on_exit then ( + List.iter ( + fun ov -> try Unix.unlink ov.ov_target_file_tmp with _ -> () + ) overlays + ) + ); + let nr_overlays = List.length overlays in + iteri ( + fun i ov -> + msg (f_"Copying disk %d/%d to %s (%s)") + (i+1) nr_overlays ov.ov_target_file ov.ov_target_format; + if verbose then printf "%s\n%!" (string_of_overlay ov); + + (* It turns out that libguestfs's disk creation code is + * considerably more flexible and easier to use than qemu-img, so + * create the disk explicitly using libguestfs then pass the + * 'qemu-img convert -n' option so qemu reuses the disk. + *) + let preallocation = ov.ov_preallocation in + let compat + match ov.ov_target_format with "qcow2" -> Some "1.1" | _ -> None in + (new G.guestfs ())#disk_create ov.ov_target_file_tmp + ov.ov_target_format ov.ov_virtual_size ?preallocation ?compat; + + let cmd + sprintf "qemu-img convert -n -f qcow2 -O %s %s %s" + (quote ov.ov_target_format) (quote ov.ov_overlay) + (quote ov.ov_target_file_tmp) in + if verbose then printf "%s\n%!" cmd; + if Sys.command cmd <> 0 then + error (f_"qemu-img command failed, see earlier errors"); + ) overlays; + + (* Create output metadata. *) + msg (f_"Creating output metadata"); + let () + (* Are we going to rename the guest? *) + let renamed_source + match output_name with + | None -> source + | Some name -> { source with s_name = name } in + match output with + | OutputLibvirt oc -> assert false + | OutputLocal dir -> + Target_local.create_metadata dir renamed_source overlays guestcaps + | OutputRHEV os -> assert false in + + (* If we wrote to a temporary file, rename to the real file. *) + List.iter ( + fun ov -> + if ov.ov_target_file_tmp <> ov.ov_target_file then + rename ov.ov_target_file_tmp ov.ov_target_file + ) overlays; + + delete_target_on_exit := false; + + msg (f_"Finishing off"); + + if debug_gc then + Gc.compact () + +and initialize_target g output output_alloc output_format overlays + let overlays + mapi ( + fun i (overlay, qemu_uri, backing_format) -> + (* Grab the virtual size of each disk. *) + let sd = "sd" ^ drive_name i in + let dev = "/dev/" ^ sd in + let vsize = g#blockdev_getsize64 dev in + + (* What output format should we use? *) + let format + match output_format, backing_format with + | Some format, _ -> format (* -of overrides everything *) + | None, Some format -> format (* same as backing format *) + | None, None -> + error (f_"disk %s (%s) has no defined format, you have to either define the original format in the source metadata, or use the '-of' option to force the output format") sd qemu_uri in + + (* What output preallocation mode should we use? *) + let preallocation + match format, output_alloc with + | "raw", `Sparse -> Some "sparse" + | "raw", `Preallocated -> Some "full" + | "qcow2", `Sparse -> Some "off" (* ? *) + | "qcow2", `Preallocated -> Some "metadata" + | _ -> None (* ignore -oa flag for other formats *) in + + { ov_overlay = overlay; + ov_target_file = ""; ov_target_file_tmp = ""; + ov_target_format = format; + ov_sd = sd; ov_virtual_size = vsize; ov_preallocation = preallocation; + ov_source_file = qemu_uri; ov_source_format = backing_format; } + ) overlays in + let overlays + match output with + | OutputLibvirt oc -> assert false + | OutputLocal dir -> Target_local.initialize dir overlays + | OutputRHEV os -> assert false in + overlays + +and inspect_source g root_choice + let roots = g#inspect_os () in + let roots = Array.to_list roots in + + let root + match roots with + | [] -> + error (f_"no root device found in this operating system image."); + | [root] -> root + | roots -> + match root_choice with + | `Ask -> + (* List out the roots and ask the user to choose. *) + printf "\n***\n"; + printf (f_"dual- or multi-boot operating system detected. Choose the root filesystem\nthat contains the main operating system from the list below:\n"); + printf "\n"; + iteri ( + fun i root -> + let prod = g#inspect_get_product_name root in + match prod with + | "unknown" -> printf " [%d] %s\n" i root + | prod -> printf " [%d] %s (%s)\n" i root prod + ) roots; + printf "\n"; + let i = ref 0 in + let n = List.length roots in + while !i < 1 || !i > n do + printf (f_"Enter number between 1 and %d: ") n; + (try i := int_of_string (read_line ()) + with + | End_of_file -> error (f_"connection closed") + | Failure "int_of_string" -> () + ) + done; + List.nth roots (!i - 1) + + | `Single -> + error (f_"multi-boot operating systems are not supported by virt-v2v. Use the --root option to change how virt-v2v handles this.") + + | `First -> + List.hd roots + + | `Dev dev -> + if List.mem dev roots then dev + else + error (f_"root device %s not found. Roots found were: %s") + dev (String.concat " " roots) in + + (* Reject this OS if it doesn't look like an installed image. *) + let () + let fmt = g#inspect_get_format root in + if fmt <> "installed" then + error (f_"libguestfs thinks this is not an installed operating system (it might be, for example, an installer disk or live CD). If this is wrong, it is probably a bug in libguestfs. root=%s fmt=%s") root fmt in + + (* Mount up the filesystems. *) + let mps = g#inspect_get_mountpoints root in + let cmp (a,_) (b,_) = compare (String.length a) (String.length b) in + let mps = List.sort cmp mps in + List.iter ( + fun (mp, dev) -> + try g#mount dev mp + with G.Error msg -> eprintf "%s (ignored)\n" msg + ) mps; + + (* Get list of applications/packages installed. *) + let apps = g#inspect_list_applications2 root in + let apps = Array.to_list apps in + + { i_root = root; i_apps = apps; } + +let () + try main () + with + | Unix.Unix_error (code, fname, "") -> (* from a syscall *) + eprintf (f_"%s: error: %s: %s\n") prog fname (Unix.error_message code); + exit 1 + | Unix.Unix_error (code, fname, param) -> (* from a syscall *) + eprintf (f_"%s: error: %s: %s: %s\n") prog fname (Unix.error_message code) + param; + exit 1 + | Sys_error msg -> (* from a syscall *) + eprintf (f_"%s: error: %s\n") prog msg; + exit 1 + | G.Error msg -> (* from libguestfs *) + eprintf (f_"%s: libguestfs error: %s\n") prog msg; + exit 1 + | Failure msg -> (* from failwith/failwithf *) + eprintf (f_"%s: failure: %s\n") prog msg; + exit 1 + | Invalid_argument msg -> (* probably should never happen *) + eprintf (f_"%s: internal error: invalid argument: %s\n") prog msg; + exit 1 + | Assert_failure (file, line, char) -> (* should never happen *) + eprintf (f_"%s: internal error: assertion failed at %s, line %d, char %d\n") prog file line char; + exit 1 + | Not_found -> (* should never happen *) + eprintf (f_"%s: internal error: Not_found exception was thrown\n") prog; + exit 1 + | exn -> (* something not matched above *) + eprintf (f_"%s: exception: %s\n") prog (Printexc.to_string exn); + exit 1 diff --git a/v2v/virt-v2v.pod b/v2v/virt-v2v.pod new file mode 100644 index 0000000..138e73b --- /dev/null +++ b/v2v/virt-v2v.pod @@ -0,0 +1,301 @@ +=head1 NAME + +virt-v2v - Convert a guest to use KVM + +=head1 SYNOPSIS + + virt-v2v -ic esx://esx.example.com/ -os imported esx_guest + + virt-v2v -ic esx://esx.example.com/ \ + -o rhev -os rhev.nfs:/export_domain --network rhevm esx_guest + + virt-v2v -i libvirtxml -o local -os /tmp guest-domain.xml + +=head1 DESCRIPTION + +Virt-v2v converts guests from a foreign hypervisor to run on KVM, +managed by libvirt or Red Hat Enterprise Virtualisation (RHEV) version +2.2 or later. It can currently convert Red Hat Enterprise Linux and +Windows guests running on Xen and VMware ESX. + +There is also a companion front-end called "virt-p2v" which comes as an +ISO or CD image that can be booted on physical machines. + +=head1 OPTIONS + +=over 4 + +=item B<--help> + +Display help. + +=item B<--debug-gc> + +Debug garbage collection and memory allocation. This is only useful +when debugging memory problems in virt-v2v or the OCaml libguestfs +bindings. + +=item B<-i libvirt> + +Set the input method to I<libvirt>. This is the default. + +In this mode you have to specify a libvirt guest name on the command +line. You may also specify a libvirt connection URI (see I<-ic>). + +=item B<-i libvirtxml> + +Set the input method to I<libvirtxml>. + +In this mode you have to pass a libvirt XML file on the command line. +This file is read in order to get metadata about the source guest +(such as its name, amount of memory), and also to locate the input +disks. + +=item B<-ic> libvirtURI + +Specify a libvirt connection URI to use when reading the guest. This +is only used when S<I<-i libvirt>>. + +Only local libvirt connections and ESX connections can be used. +Remote libvirt connections will not work in general. + +=item B<--machine-readable> + +This option is used to make the output more machine friendly +when being parsed by other programs. See +L</MACHINE READABLE OUTPUT> below. + +=item B<-o libvirt> + +Set the output method to I<libvirt>. This is the default. + +In this mode, the converted guest is created as a libvirt guest. You +may also specify a libvirt connection URI (see I<-oc>). + +=item B<-o local> + +Set the output method to I<local>. + +In this mode, the converted guest is written to a local directory +specified by I<-os /dir> (the directory must exist). The converted +guest's disks are written as: + + /dir/disk-sda + /dir/disk-sdb + [etc] + +and a libvirt XML file is created containing guest metadata +(C</dir/name.xml>, where C<name> is the guest name). + +=item B<-o rhev> + +Set the output method to I<rhev>. + +The converted guest is written to a RHEV Export Storage Domain. The +I<-os> parameter must also be used to specify the location of the +Export Storage Domain. Note this does not actually import the guest +into RHEV. You have to do that manually later using the UI. + +=item B<-oa sparse> + +=item B<-oa preallocated> + +Set the output file allocation mode. The default is C<sparse>. + +=item B<-oc> libvirtURI + +Specify a libvirt connection to use when writing the converted guest. +This is only used when S<I<-o libvirt>>. + +Only local libvirt connections can be used. Remote libvirt +connections will not work. + +=item B<-of> format + +When converting the guest, convert the disks to the given format. + +If not specified, then the input format is used. + +=item B<-on> name + +Rename the guest when converting it. If this option is not used then +the output name is the same as the input name. + +=item B<-os> storage + +The location of the storage for the converted guest. + +For I<-o libvirt>, this is a libvirt pool (see S<C<virsh pool-list>>). + +For I<-o local>, this is a directory name. The directory must exist. + +For I<-o rhev>, this is an NFS path of the form +C<E<lt>hostE<gt>:E<lt>pathE<gt>>, eg: + + rhev-storage.example.com:/rhev/export + +The NFS export must be mountable and writable by the user and host +running virt-v2v, since the virt-v2v program has to actually mount it +when it runs. + +=item B<-q> + +=item B<--quiet> + +This disables progress bars and other unnecessary output. + +=item B<--root ask> + +=item B<--root single> + +=item B<--root first> + +=item B<--root> /dev/sdX + +=item B<--root> /dev/VG/LV + +Choose the root filesystem to be converted. + +In the case where the virtual machine is dual-boot or multi-boot, or +where the VM has other filesystems that look like operating systems, +this option can be used to select the root filesystem (a.k.a. C<C:> +drive or C</>) of the operating system that is to be converted. The +Windows Recovery Console, certain attached DVD drives, and bugs in +libguestfs inspection heuristics, can make a guest look like a +multi-boot operating system. + +The default in virt-v2v E<le> 0.7.1 was S<I<--root single>>, which +causes virt-v2v to die if a multi-boot operating system is found. + +Since virt-v2v E<ge> 0.7.2 the default is now S<I<--root ask>>: If the +VM is found to be multi-boot, then virt-v2v will stop and list the +possible root filesystems and ask the user which to use. This +requires that virt-v2v is run interactively. + +S<I<--root first>> means to choose the first root device in the case +of a multi-boot operating system. Since this is a heuristic, it may +sometimes choose the wrong one. + +You can also name a specific root device, eg. S<I<--root /dev/sda2>> +would mean to use the second partition on the first hard drive. If +the named root device does not exist or was not detected as a root +device, then virt-v2v will fail. + +Note that there is a bug in grub which prevents it from successfully +booting a multiboot system if VirtIO is enabled. Grub is only able to +boot an operating system from the first VirtIO disk. Specifically, +C</boot> must be on the first VirtIO disk, and it cannot chainload an +OS which is not in the first VirtIO disk. + +=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. + +=back + +=head1 MACHINE READABLE OUTPUT + +The I<--machine-readable> option can be used to make the output more +machine friendly, which is useful when calling virt-v2v from +other programs, GUIs etc. + +There are two ways to use this option. + +Firstly use the option on its own to query the capabilities of the +virt-v2v binary. Typical output looks like this: + + $ virt-v2v --machine-readable + virt-v2v + libguestfs-rewrite + +A list of features is printed, one per line, and the program exits +with status 0. + +Secondly use the option in conjunction with other options to make the +regular program output more machine friendly. + +At the moment this means: + +=over 4 + +=item 1. + +Progress bar messages can be parsed from stdout by looking for this +regular expression: + + ^[0-9]+/[0-9]+$ + +=item 2. + +The calling program should treat messages sent to stdout (except for +progress bar messages) as status messages. They can be logged and/or +displayed to the user. + +=item 3. + +The calling program should treat messages sent to stderr as error +messages. In addition, virt-v2v exits with a non-zero status +code if there was a fatal error. + +=back + +Virt-v2v E<le> 0.9.1 did not support the I<--machine-readable> +option at all. The option was added when virt-v2v was rewritten in 2014. + +=head1 ENVIRONMENT VARIABLES + +=over 4 + +=item TMPDIR + +Location of the temporary directory used for the potentially large +temporary overlay file. + +You should ensure there is enough free space in the worst case for a +full copy of the source disk (I<virtual> size), or else set C<$TMPDIR> +to point to another directory that has enough space. + +This defaults to C</tmp>. + +Note that if C<$TMPDIR> is a tmpfs (eg. if C</tmp> is on tmpfs, or if +you use C<TMPDIR=/dev/shm>), tmpfs defaults to a maximum size of +I<half> of physical RAM. If virt-v2v exceeds this, it will hang. +The solution is either to use a real disk, or to increase the maximum +size of the tmpfs mountpoint, eg: + + mount -o remount,size=10G /tmp + +=back + +For other environment variables, see L<guestfs(3)/ENVIRONMENT VARIABLES>. + +=head1 SEE ALSO + +L<virt-df(1)>, +L<virt-filesystems(1)>, +L<guestfs(3)>, +L<guestfish(1)>, +L<qemu-img(1)>, +L<http://libguestfs.org/>. + +=head1 AUTHORS + +Richard W.M. Jones L<http://people.redhat.com/~rjones/> + +Matthew Booth + +=head1 COPYRIGHT + +Copyright (C) 2009-2014 Red Hat Inc. diff --git a/v2v/xml-c.c b/v2v/xml-c.c new file mode 100644 index 0000000..9b79c6b --- /dev/null +++ b/v2v/xml-c.c @@ -0,0 +1,240 @@ +/* virt-v2v + * Copyright (C) 2009-2014 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. + */ + +/* Mini interface to libxml2 for parsing libvirt XML. */ + +#include <config.h> + +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> + +#include <caml/alloc.h> +#include <caml/custom.h> +#include <caml/fail.h> +#include <caml/memory.h> +#include <caml/mlvalues.h> + +#include <libxml/xpath.h> + +#include "guestfs.h" +#include "guestfs-internal-frontend.h" + +/* xmlDocPtr type */ +#define Doc_val(v) (*((xmlDocPtr *)Data_custom_val(v))) + +static void +doc_finalize (value docv) +{ + xmlDocPtr doc = Doc_val (docv); + + if (doc) + xmlFreeDoc (doc); +} + +static struct custom_operations doc_custom_operations = { + (char *) "doc_custom_operations", + doc_finalize, + custom_compare_default, + custom_hash_default, + custom_serialize_default, + custom_deserialize_default +}; + +/* xmlXPathContextPtr type */ +#define Xpathctx_val(v) (*((xmlXPathContextPtr *)Data_custom_val(v))) + +static void +xpathctx_finalize (value xpathctxv) +{ + xmlXPathContextPtr xpathctx = Xpathctx_val (xpathctxv); + + if (xpathctx) + xmlXPathFreeContext (xpathctx); +} + +static struct custom_operations xpathctx_custom_operations = { + (char *) "xpathctx_custom_operations", + xpathctx_finalize, + custom_compare_default, + custom_hash_default, + custom_serialize_default, + custom_deserialize_default +}; + +/* xmlXPathObjectPtr type */ +#define Xpathobj_val(v) (*((xmlXPathObjectPtr *)Data_custom_val(v))) + +static void +xpathobj_finalize (value xpathobjv) +{ + xmlXPathObjectPtr xpathobj = Xpathobj_val (xpathobjv); + + if (xpathobj) + xmlXPathFreeObject (xpathobj); +} + +static struct custom_operations xpathobj_custom_operations = { + (char *) "xpathobj_custom_operations", + xpathobj_finalize, + custom_compare_default, + custom_hash_default, + custom_serialize_default, + custom_deserialize_default +}; + +value +v2v_xml_parse_memory (value xmlv) +{ + CAMLparam1 (xmlv); + CAMLlocal1 (docv); + xmlDocPtr doc; + + doc = xmlParseMemory (String_val (xmlv), caml_string_length (xmlv)); + if (doc == NULL) + caml_invalid_argument ("parse_memory: unable to parse XML from libvirt"); + + docv = caml_alloc_custom (&doc_custom_operations, sizeof (xmlDocPtr), 0, 1); + Doc_val (docv) = doc; + + CAMLreturn (docv); +} + +value +v2v_xml_xpath_new_context (value docv) +{ + CAMLparam1 (docv); + CAMLlocal1 (xpathctxv); + xmlDocPtr doc; + xmlXPathContextPtr xpathctx; + + doc = Doc_val (docv); + xpathctx = xmlXPathNewContext (doc); + if (xpathctx == NULL) + caml_invalid_argument ("xpath_new_context: unable to create xmlXPathNewContext"); + + xpathctxv = caml_alloc_custom (&xpathctx_custom_operations, + sizeof (xmlXPathContextPtr), 0, 1); + Xpathctx_val (xpathctxv) = xpathctx; + + CAMLreturn (xpathctxv); +} + +value +v2v_xml_xpath_eval_expression (value xpathctxv, value exprv) +{ + CAMLparam2 (xpathctxv, exprv); + CAMLlocal1 (xpathobjv); + xmlXPathContextPtr xpathctx; + xmlXPathObjectPtr xpathobj; + + xpathctx = Xpathctx_val (xpathctxv); + xpathobj = xmlXPathEvalExpression (BAD_CAST String_val (exprv), xpathctx); + if (xpathobj == NULL) + caml_invalid_argument ("xpath_eval_expression: unable to evaluate XPath expression"); + + xpathobjv = caml_alloc_custom (&xpathobj_custom_operations, + sizeof (xmlXPathObjectPtr), 0, 1); + Xpathobj_val (xpathobjv) = xpathobj; + + CAMLreturn (xpathobjv); +} + +value +v2v_xml_xpathobj_nr_nodes (value xpathobjv) +{ + CAMLparam1 (xpathobjv); + xmlXPathObjectPtr xpathobj = Xpathobj_val (xpathobjv); + + CAMLreturn (Val_int (xpathobj->nodesetval->nodeNr)); +} + +value +v2v_xml_xpathobj_get_node_ptr (value xpathobjv, value iv) +{ + CAMLparam2 (xpathobjv, iv); + xmlXPathObjectPtr xpathobj = Xpathobj_val (xpathobjv); + int i = Int_val (iv); + + /* Because xmlNodePtrs are owned by the document, we don't want to + * wrap this up with a finalizer, so just pass the pointer straight + * back to OCaml as a value. OCaml will ignore it because it's + * outside the heap, and just pass it back to us when needed. This + * relies on the xmlDocPtr not being freed, but we pair the node + * pointer with the doc in the OCaml layer so the GC will not free + * one without freeing the other. + */ + CAMLreturn ((value) xpathobj->nodesetval->nodeTab[i]); +} + +value +v2v_xml_xpathctx_set_node_ptr (value xpathctxv, value nodev) +{ + CAMLparam2 (xpathctxv, nodev); + xmlXPathContextPtr xpathctx = Xpathctx_val (xpathctxv); + xmlNodePtr node = (xmlNodePtr) nodev; + + xpathctx->node = node; + + CAMLreturn (Val_unit); +} + +value +v2v_xml_node_ptr_name (value nodev) +{ + CAMLparam1 (nodev); + xmlNodePtr node = (xmlNodePtr) nodev; + + switch (node->type) { + case XML_ATTRIBUTE_NODE: + case XML_ELEMENT_NODE: + CAMLreturn (caml_copy_string ((char *) node->name)); + + default: + caml_invalid_argument ("node_name: don't know how to get the name of this node"); + } +} + +value +v2v_xml_node_ptr_as_string (value docv, value nodev) +{ + CAMLparam2 (docv, nodev); + xmlDocPtr doc = Doc_val (docv); + xmlNodePtr node = (xmlNodePtr) nodev; + CLEANUP_FREE char *str = NULL; + + switch (node->type) { + case XML_TEXT_NODE: + case XML_COMMENT_NODE: + case XML_CDATA_SECTION_NODE: + case XML_PI_NODE: + CAMLreturn (caml_copy_string ((char *) node->content)); + + case XML_ATTRIBUTE_NODE: + case XML_ELEMENT_NODE: + str = (char *) xmlNodeListGetString (doc, node->children, 1); + + if (str == NULL) + caml_invalid_argument ("node_as_string: xmlNodeListGetString cannot convert node to string"); + + CAMLreturn (caml_copy_string (str)); + + default: + caml_invalid_argument ("node_as_string: don't know how to convert this node to a string"); + } +} diff --git a/v2v/xml.ml b/v2v/xml.ml new file mode 100644 index 0000000..5cd75c1 --- /dev/null +++ b/v2v/xml.ml @@ -0,0 +1,50 @@ +(* virt-v2v + * Copyright (C) 2009-2014 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. + *) + +(* Mini interface to libxml2 for parsing libvirt XML. *) + +type doc +type node_ptr +type xpathctx +type xpathobj + +(* Since node is owned by doc, we have to make that explicit to the + * garbage collector. + *) +type node = doc * node_ptr + +external parse_memory : string -> doc = "v2v_xml_parse_memory" +external xpath_new_context : doc -> xpathctx = "v2v_xml_xpath_new_context" +external xpath_eval_expression : xpathctx -> string -> xpathobj = "v2v_xml_xpath_eval_expression" + +external xpathobj_nr_nodes : xpathobj -> int = "v2v_xml_xpathobj_nr_nodes" +external xpathobj_get_node_ptr : xpathobj -> int -> node_ptr = "v2v_xml_xpathobj_get_node_ptr" +let xpathobj_node doc xpathobj i + let n = xpathobj_get_node_ptr xpathobj i in + (doc, n) + +external xpathctx_set_node_ptr : xpathctx -> node_ptr -> unit = "v2v_xml_xpathctx_set_node_ptr" +let xpathctx_set_current_context xpathctx (_, node) + xpathctx_set_node_ptr xpathctx node + +external node_ptr_name : node_ptr -> string = "v2v_xml_node_ptr_name" +let node_name (_, node) = node_ptr_name node + +external node_ptr_as_string : doc -> node_ptr -> string = "v2v_xml_node_ptr_as_string" +let node_as_string (doc, node) + node_ptr_as_string doc node diff --git a/v2v/xml.mli b/v2v/xml.mli new file mode 100644 index 0000000..c4363ad --- /dev/null +++ b/v2v/xml.mli @@ -0,0 +1,57 @@ +(* virt-v2v + * Copyright (C) 2009-2014 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. + *) + +(** Mini interface to libxml2 for parsing libvirt XML. *) + +type doc (** xmlDocPtr *) +type node (** xmlNodePtr *) +type xpathctx (** xmlXPathContextPtr *) +type xpathobj (** xmlXPathObjectPtr *) + +val parse_memory : string -> doc +(** xmlParseMemory *) +val xpath_new_context : doc -> xpathctx +(** xmlXPathNewContext *) +val xpath_eval_expression : xpathctx -> string -> xpathobj +(** xmlXPathEvalExpression *) + +val xpathobj_nr_nodes : xpathobj -> int +(** Get the number of nodes in the node set of the xmlXPathObjectPtr. *) +val xpathobj_node : doc -> xpathobj -> int -> node +(** Get the number of nodes in the node set of the xmlXPathObjectPtr. *) + +val xpathctx_set_current_context : xpathctx -> node -> unit +(** Set the current context of an xmlXPathContextPtr to the node. + Basically the same as the following C code: + + {v + xpathctx->node = node + v} + + It means the next expression you evaluate within this context will + start at this node, when evaluating relative paths + (eg. [./@attr]). +*) + +val node_name : node -> string +(** Get the name of the node. Note that only things like elements and + attributes have names. Other types of nodes will return an + error. *) + +val node_as_string : node -> string +(** Converter to turn a node into a string *) -- 1.8.5.3