From f9de01b9b97e3d13959754446464f9b768deea81 Mon Sep 17 00:00:00 2001 From: Richard Jones Date: Wed, 22 Jul 2009 20:58:39 +0100 Subject: [PATCH] Add libguestfs-test-tool. This is an end-user testing tool, designed to test basic functionality of libguestfs/qemu/kernel combination on the end-user's final host machine. It does not perform a thorough test, but should be enough to find most booting issues. Also this is intended to be used when reporting bugs. --- .gitignore | 3 + HACKING | 3 + Makefile.am | 2 +- configure.ac | 1 + guestfs.pod | 5 + po/POTFILES.in | 2 + test-tool/Makefile.am | 44 ++++ test-tool/README | 9 + test-tool/helper.c | 73 +++++++ test-tool/libguestfs-test-tool.pod | 136 ++++++++++++ test-tool/test-tool.c | 437 +++++++++++++++++++++++++++++++++++++ 11 files changed, 714 insertions(+), 1 deletion(-) create mode 100644 test-tool/Makefile.am create mode 100644 test-tool/README create mode 100644 test-tool/helper.c create mode 100644 test-tool/libguestfs-test-tool.pod create mode 100644 test-tool/test-tool.c diff --git a/.gitignore b/.gitignore index b12dda6..d66ed99 100644 --- a/.gitignore +++ b/.gitignore @@ -169,4 +169,7 @@ src/guestfs-structs.h src/.pod2text.data src/stamp-generator stamp-h1 +test-tool/libguestfs-test-tool.1 +test-tool/libguestfs-test-tool +test-tool/libguestfs-test-tool-helper v2v/virt-v2v.1 diff --git a/HACKING b/HACKING index 6ca33be..42f4f77 100644 --- a/HACKING +++ b/HACKING @@ -91,6 +91,9 @@ src/ Source code to the C library. Also contains the crucial generator program. +test-tool/ + Interactive qemu/kernel test tool. + v2v/ Xen to KVM (V2V) conversion tool. diff --git a/Makefile.am b/Makefile.am index 1f6f62e..f118291 100644 --- a/Makefile.am +++ b/Makefile.am @@ -18,7 +18,7 @@ ACLOCAL_AMFLAGS = -I m4 SUBDIRS = src daemon appliance fish po examples images \ - capitests regressions + capitests regressions test-tool if HAVE_OCAML SUBDIRS += ocaml diff --git a/configure.ac b/configure.ac index 1bc1109..bc60608 100644 --- a/configure.ac +++ b/configure.ac @@ -574,6 +574,7 @@ AC_CONFIG_FILES([Makefile images/Makefile capitests/Makefile regressions/Makefile + test-tool/Makefile ocaml/Makefile ocaml/examples/Makefile perl/Makefile python/Makefile diff --git a/guestfs.pod b/guestfs.pod index 4235454..6bdc941 100644 --- a/guestfs.pod +++ b/guestfs.pod @@ -982,6 +982,11 @@ That you are testing a recent version. Describe the bug accurately, and give a way to reproduce it. +=item * + +Run libguestfs-test-tool and paste the B +output into the bug report. + =back =head1 AUTHORS diff --git a/po/POTFILES.in b/po/POTFILES.in index 7ad1e12..ca01b3d 100644 --- a/po/POTFILES.in +++ b/po/POTFILES.in @@ -74,4 +74,6 @@ ruby/ext/guestfs/_guestfs.c src/guestfs-actions.c src/guestfs-bindtests.c src/guestfs.c +test-tool/helper.c +test-tool/test-tool.c v2v/virt-v2v.pl diff --git a/test-tool/Makefile.am b/test-tool/Makefile.am new file mode 100644 index 0000000..510a42f --- /dev/null +++ b/test-tool/Makefile.am @@ -0,0 +1,44 @@ +# libguestfs +# Copyright (C) 2009 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., 675 Mass Ave, Cambridge, MA 02139, USA. + +EXTRA_DIST = libguestfs-test-tool.pod + +CLEANFILES = + +bin_PROGRAMS = libguestfs-test-tool +libexec_PROGRAMS = libguestfs-test-tool-helper +man_MANS = libguestfs-test-tool.1 + +AM_CPPFLAGS = \ + -DDEFAULT_HELPER='"$(libexecdir)/libguestfs-test-tool-helper"' + +libguestfs_test_tool_SOURCES = test-tool.c +libguestfs_test_tool_CFLAGS = \ + -I$(top_srcdir)/src -I$(top_builddir)/src \ + -Wall +libguestfs_test_tool_LDADD = \ + $(top_builddir)/src/libguestfs.la + +libguestfs_test_tool_helper_SOURCES = helper.c +libguestfs_test_tool_helper_LDFLAGS = -all-static + +libguestfs-test-tool.1: libguestfs-test-tool.pod + $(POD2MAN) \ + --section 1 \ + -c "Virtualization Support" \ + --release "$(PACKAGE_NAME)-$(PACKAGE_VERSION)" \ + $< > $@ diff --git a/test-tool/README b/test-tool/README new file mode 100644 index 0000000..a98a02d --- /dev/null +++ b/test-tool/README @@ -0,0 +1,9 @@ +This is a test tool, not a comprehensive test, but a "does it +basically function" test, which can be packaged and given to end +users. + +It also collects and prints a lot of internal debug information, which +is useful in bug reports to track down appliance / qemu boot problems. + +For more information, please read the man page +libguestfs-test-tool(1). diff --git a/test-tool/helper.c b/test-tool/helper.c new file mode 100644 index 0000000..3395f21 --- /dev/null +++ b/test-tool/helper.c @@ -0,0 +1,73 @@ +/* libguestfs-test-tool-helper + * Copyright (C) 2009 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +/* NB. This program is intended to run inside the appliance. */ + +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +char buffer[10 * 1024]; + +int +main (void) +{ + int fd; + + fprintf (stderr, "This is the libguestfs-test-tool helper program.\n"); + + /* This should fail immediately if we're not in the appliance. */ + if (mkdir ("/tmp", 0700) == -1) { + perror ("mkdir"); + fprintf (stderr, "This program should not be run directly. Use libguestfs-test-tool instead.\n"); + exit (1); + } + + if (geteuid () != 0) { + fprintf (stderr, "helper: This program doesn't appear to be running as root.\n"); + exit (1); + } + + if (mkdir ("/tmp/helper", 0700) == -1) { + perror ("/tmp/helper"); + exit (1); + } + + fd = open ("/tmp/helper/a", O_CREAT|O_EXCL|O_WRONLY, 0600); + if (fd == -1) { + perror ("create /tmp/helper/a"); + exit (1); + } + if (write (fd, buffer, sizeof buffer) != sizeof buffer) { + perror ("write"); + exit (1); + } + if (close (fd) == -1) { + perror ("close"); + exit (1); + } + + exit (0); +} diff --git a/test-tool/libguestfs-test-tool.pod b/test-tool/libguestfs-test-tool.pod new file mode 100644 index 0000000..df8c77b --- /dev/null +++ b/test-tool/libguestfs-test-tool.pod @@ -0,0 +1,136 @@ +=encoding utf8 + +=head1 NAME + +libguestfs-test-tool - End user tests for libguestfs + +=head1 SYNOPSIS + + libguestfs-test-tool [--options] + +=head1 DESCRIPTION + +libguestfs-test-tool is a test program shipped with libguestfs to end +users and developers, to allow them to check basic libguestfs +functionality is working. This is needed because libguestfs +occasionally breaks for reasons beyond our control: usually because of +changes in the underlying qemu or kernel packages, or the host +environment. + +If you suspect a problem in libguestfs, then just run: + + libguestfs-test-tool + +It will print lots of diagnostic messages. + +If it runs to completion successfully, you will see this near the end: + + ===== TEST FINISHED OK ===== + +and the test tool will exit with code 0. + +If it fails (and/or exits with non-zero error code), please paste the +B output of the test tool into a bug report. More +information about reporting bugs can be found on the +L website. + +=head1 OPTIONS + +=over 4 + +=item I<--help> + +Display short usage information and exit. + +=item I<--helper /path/to/libguestfs-test-tool-helper> + +Pass an alternate name for the helper program. libguestfs-test-tool +will normally look in the C<$libexec> directory that was configured +when the tool was built. + +=item I<--qemu qemu_binary> + +If you have downloaded another qemu binary, point this option at the +full path of the binary to try it. + +=item I<--qemudir qemu_source_dir> + +If you have compiled qemu from source, point this option at the source +directory to try it. + +=item I<--timeout N> + +Set the launch timeout to C seconds. The default is 120 seconds +which does not usually need to be adjusted unless your machine is very +slow. + +=back + +=head1 TRYING OUT A DIFFERENT VERSION OF QEMU + +If you have compiled another version of qemu from source and would +like to try that, then you can use the I<--qemudir> option to point to +the qemu source directory. + +If you have downloaded a qemu binary from somewhere, use the I<--qemu> +option to point to the binary. + +When using an alternate qemu with libguestfs, usually you would need +to write a qemu wrapper script (see section I in +L). libguestfs-test-tool writes a temporary qemu wrapper +script when you use either of the I<--qemudir> or I<--qemu> options. + +=head1 EXIT CODE + +libguestfs-test-tool returns I<0> if the tests completed without +error, or I<1> if there was an error. + +=head1 FILES + +=over 4 + +=item /usr/libexec/libguestfs-test-tool-helper + +This helper program is run inside the appliance and provides +additional tests. + +=item /usr/bin/mkisofs + +The C command is required in order to construct a CD-ROM ISO +file which is used as part of the tests. + +=back + +=head1 ENVIRONMENT VARIABLES + +For the full list of environment variables which may affect +libguestfs, please see the L manual page. + +=head1 SEE ALSO + +L, +L, +L. + +=head1 AUTHORS + +Richard W.M. Jones (C) + +=head1 COPYRIGHT + +Copyright (C) 2009 Red Hat Inc. +L + +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., 675 Mass Ave, Cambridge, MA 02139, USA. diff --git a/test-tool/test-tool.c b/test-tool/test-tool.c new file mode 100644 index 0000000..9617073 --- /dev/null +++ b/test-tool/test-tool.c @@ -0,0 +1,437 @@ +/* libguestfs-test-tool + * Copyright (C) 2009 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#ifdef HAVE_GETTEXT +#include "gettext.h" +#define _(str) dgettext(PACKAGE, (str)) +#define N_(str) dgettext(PACKAGE, (str)) +#else +#define _(str) str +#define N_(str) str +#endif + +#define DEFAULT_TIMEOUT 120 + +static const char *helper = DEFAULT_HELPER; +static int timeout = DEFAULT_TIMEOUT; +static char tmpf[] = "/tmp/libguestfs-test-tool-sda-XXXXXX"; +static char isof[] = "/tmp/libguestfs-test-tool-iso-XXXXXX"; +static guestfs_h *g; + +static void preruncheck (void); +static void make_files (void); +static void set_qemu (const char *path, int use_wrapper); + +static void +usage (void) +{ + printf (_("libguestfs-test-tool: interactive test tool\n" + "Copyright (C) 2009 Red Hat Inc.\n" + "Usage:\n" + " libguestfs-test-tool [--options]\n" + "Options:\n" + " --help Display usage\n" + " --helper libguestfs-test-tool-helper\n" + " Helper program (default: %s)\n" + " --qemudir dir Specify QEMU source directory\n" + " --qemu qemu Specify QEMU binary\n" + " --timeout n\n" + " -t n Set launch timeout (default: %d seconds)\n" + ), + DEFAULT_HELPER, DEFAULT_TIMEOUT); +} + +int +main (int argc, char *argv[]) +{ + static const char *options = "?"; + static struct option long_options[] = { + { "help", 0, 0, '?' }, + { "helper", 1, 0, 0 }, + { "qemu", 1, 0, 0 }, + { "qemudir", 1, 0, 0 }, + { "timeout", 1, 0, 't' }, + { 0, 0, 0, 0 } + }; + int c; + int option_index; + extern char **environ; + int i; + struct guestfs_version *vers; + char *sfdisk_lines[] = { ",", NULL }; + char *str; + /* XXX This is wrong if the user renames the helper. */ + char *helper_args[] = { "/iso/libguestfs-test-tool-helper", NULL }; + + for (;;) { + c = getopt_long (argc, argv, options, long_options, &option_index); + if (c == -1) break; + + switch (c) { + case 0: /* options which are long only */ + if (strcmp (long_options[option_index].name, "helper") == 0) + helper = optarg; + else if (strcmp (long_options[option_index].name, "qemu") == 0) + set_qemu (optarg, 0); + else if (strcmp (long_options[option_index].name, "qemudir") == 0) + set_qemu (optarg, 1); + else { + fprintf (stderr, + _("libguestfs-test-tool: unknown long option: %s (%d)\n"), + long_options[option_index].name, option_index); + exit (1); + } + break; + + case 't': + if (sscanf (optarg, "%d", &timeout) != 1 || timeout < 0) { + fprintf (stderr, + _("libguestfs-test-tool: invalid timeout: %s\n"), + optarg); + exit (1); + } + break; + + case '?': + usage (); + exit (0); + + default: + fprintf (stderr, + _("libguestfs-test-tool: unexpected command line option 0x%x\n"), + c); + exit (1); + } + } + + preruncheck (); + make_files (); + + printf ("===== Test starts here =====\n"); + + /* Must set LIBGUESTFS_DEBUG=1 */ + setenv ("LIBGUESTFS_DEBUG", "1", 1); + + /* Print out any environment variables which may relate to this test. */ + for (i = 0; environ[i] != NULL; ++i) + if (strncmp (environ[i], "LIBGUESTFS_", 11) == 0) + printf ("%s\n", environ[i]); + + /* Create the handle and configure it. */ + g = guestfs_create (); + if (g == NULL) { + fprintf (stderr, + _("libguestfs-test-tool: failed to create libguestfs handle\n")); + exit (1); + } + if (guestfs_add_drive (g, tmpf) == -1) { + fprintf (stderr, + _("libguestfs-test-tool: failed to add drive '%s'\n"), + tmpf); + exit (1); + } + if (guestfs_add_drive (g, isof) == -1) { + fprintf (stderr, + _("libguestfs-test-tool: failed to add drive '%s'\n"), + isof); + exit (1); + } + + /* Print any version info etc. */ + vers = guestfs_version (g); + if (vers == NULL) { + fprintf (stderr, _("libguestfs-test-tool: guestfs_version failed\n")); + exit (1); + } + printf ("library version: %"PRIi64".%"PRIi64".%"PRIi64"%s\n", + vers->major, vers->minor, vers->release, vers->extra); + guestfs_free_version (vers); + + printf ("guestfs_get_append: %s\n", guestfs_get_append (g) ? : "(null)"); + printf ("guestfs_get_autosync: %d\n", guestfs_get_autosync (g)); + printf ("guestfs_get_memsize: %d\n", guestfs_get_memsize (g)); + printf ("guestfs_get_path: %s\n", guestfs_get_path (g)); + printf ("guestfs_get_qemu: %s\n", guestfs_get_qemu (g)); + printf ("guestfs_get_verbose: %d\n", guestfs_get_verbose (g)); + + /* Launch the guest handle. */ + if (guestfs_launch (g) == -1) { + fprintf (stderr, + _("libguestfs-test-tool: failed to launch appliance\n")); + exit (1); + } + + printf ("Launching appliance, timeout set to %d seconds.\n", timeout); + fflush (stdout); + + alarm (timeout); + + if (guestfs_wait_ready (g) == -1) { + fprintf (stderr, + _("libguestfs-test-tool: failed or timed out in 'wait_ready'\n")); + exit (1); + } + + alarm (0); + + printf ("Guest launched OK.\n"); + fflush (stdout); + + /* Create the filesystem and mount everything. */ + if (guestfs_sfdiskM (g, "/dev/sda", sfdisk_lines) == -1) { + fprintf (stderr, + _("libguestfs-test-tool: failed to run sfdisk\n")); + exit (1); + } + + if (guestfs_mkfs (g, "ext2", "/dev/sda1") == -1) { + fprintf (stderr, + _("libguestfs-test-tool: failed to mkfs.ext2\n")); + exit (1); + } + + if (guestfs_mount (g, "/dev/sda1", "/") == -1) { + fprintf (stderr, + _("libguestfs-test-tool: failed to mount /dev/sda1 on /\n")); + exit (1); + } + + if (guestfs_mkdir (g, "/iso") == -1) { + fprintf (stderr, + _("libguestfs-test-tool: failed to mkdir /iso\n")); + exit (1); + } + + if (guestfs_mount (g, "/dev/sdb", "/iso") == -1) { + fprintf (stderr, + _("libguestfs-test-tool: failed to mount /dev/sdb on /iso\n")); + exit (1); + } + + /* Let's now run some simple tests using the helper program. */ + str = guestfs_command (g, helper_args); + if (str == NULL) { + fprintf (stderr, + _("libguestfs-test-tool: could not run helper program, or helper failed\n")); + exit (1); + } + free (str); + + printf ("===== TEST FINISHED OK =====\n"); + exit (0); +} + +static char qemuwrapper[] = "/tmp/libguestfs-test-tool-wrapper-XXXXXX"; + +static void +cleanup_wrapper (void) +{ + unlink (qemuwrapper); +} + +/* Handle the --qemu and --qemudir parameters. use_wrapper is true + * in the --qemudir (source directory) case, where we have to create + * a wrapper shell script. + */ +static void +set_qemu (const char *path, int use_wrapper) +{ + char buffer[PATH_MAX]; + struct stat statbuf; + int fd; + FILE *fp; + + if (getenv ("LIBGUESTFS_QEMU")) { + fprintf (stderr, + _("LIBGUESTFS_QEMU environment variable is already set, so\n" + "--qemu/--qemudir options cannot be used.\n")); + exit (1); + } + + if (!use_wrapper) { + if (access (path, X_OK) == -1) { + fprintf (stderr, + _("Binary '%s' does not exist or is not executable\n"), + path); + exit (1); + } + + setenv ("LIBGUESTFS_QEMU", path, 1); + return; + } + + /* This should be a source directory, so check it. */ + snprintf (buffer, sizeof buffer, "%s/pc-bios", path); + if (stat (buffer, &statbuf) == -1 || + !S_ISDIR (statbuf.st_mode)) { + fprintf (stderr, + _("%s: does not look like a qemu source directory\n"), + path); + exit (1); + } + + /* Make a wrapper script. */ + fd = mkstemp (qemuwrapper); + if (fd == -1) { + perror (qemuwrapper); + exit (1); + } + + fchmod (fd, 0700); + + fp = fdopen (fd, "w"); + fprintf (fp, + "#!/bin/sh -\n" + "qemudir='%s'\n" + "\"$qemudir\"/", + path); + + /* Select the right qemu binary for the wrapper script. */ +#ifdef __i386__ + fprintf (fp, "i386-softmmu/qemu"); +#else + fprintf (fp, host_cpu "-softmmu/qemu-system-" host_cpu); +#endif + + fprintf (fp, " -L \"$qemudir\"/pc-bios \"$@\"\n"); + + fclose (fp); + + setenv ("LIBGUESTFS_QEMU", qemuwrapper, 1); + atexit (cleanup_wrapper); +} + +/* After getting the command line args, but before running + * anything, we check everything is in place to do the tests. + */ +static void +preruncheck (void) +{ + int r; + FILE *fp; + char cmd[256]; + char buffer[1024]; + + if (access (helper, R_OK) == -1) { + fprintf (stderr, + _("Test tool helper program 'libguestfs-test-tool-helper' is not\n" + "available. Expected to find it in '%s'\n" + "\n" + "Use the --helper option to specify the location of this program.\n"), + helper); + exit (1); + } + + snprintf (cmd, sizeof cmd, "file '%s'", helper); + fp = popen (cmd, "r"); + if (fp == NULL) { + perror (cmd); + exit (1); + } + r = fread (buffer, 1, sizeof buffer - 1, fp); + if (r == 0) { + fprintf (stderr, _("command failed: %s"), cmd); + exit (1); + } + pclose (fp); + buffer[r] = '\0'; + + if (strstr (buffer, "statically linked") == NULL) { + fprintf (stderr, + _("Test tool helper program %s\n" + "is not statically linked. This is a build error when this test tool\n" + "was built.\n"), + helper); + exit (1); + } +} + +static void +cleanup_tmpfiles (void) +{ + unlink (tmpf); + unlink (isof); +} + +static void +make_files (void) +{ + int fd, r; + char cmd[256]; + + /* Make the ISO which will contain the helper program. */ + fd = mkstemp (isof); + if (fd == -1) { + perror (isof); + exit (1); + } + close (fd); + + snprintf (cmd, sizeof cmd, "mkisofs -quiet -rJT -o '%s' '%s'", + isof, helper); + r = system (cmd); + if (r == -1 || WEXITSTATUS(r) != 0) { + fprintf (stderr, + _("mkisofs command failed: %s\n"), cmd); + exit (1); + } + + /* Allocate the sparse file for /dev/sda. */ + fd = mkstemp (tmpf); + if (fd == -1) { + perror (tmpf); + unlink (isof); + exit (1); + } + + if (lseek (fd, 100 * 1024 * 1024 - 1, SEEK_SET) == -1) { + perror ("lseek"); + close (fd); + unlink (tmpf); + unlink (isof); + exit (1); + } + + if (write (fd, "\0", 1) == -1) { + perror ("write"); + close (fd); + unlink (tmpf); + unlink (isof); + exit (1); + } + + close (fd); + + atexit (cleanup_tmpfiles); /* Removes tmpf and isof. */ +} -- 1.8.3.1