From de5924a2294a6aeb09e4b41b55f06d2f3ea5e0ec Mon Sep 17 00:00:00 2001 From: "Richard W.M. Jones" Date: Thu, 17 Jul 2014 20:32:24 +0000 Subject: [PATCH] tests: Add test program to run qemu/appliance repeatedly. For example: $ time ./run ./tests/qemu/qemu-boot -n 100 real 1m19.794s user 0m10.001s sys 0m5.928s will run 100 appliance start/stops, from multiple threads. --- .gitignore | 1 + tests/qemu/Makefile.am | 27 ++++- tests/qemu/qemu-boot.c | 270 +++++++++++++++++++++++++++++++++++++++++ 3 files changed, 297 insertions(+), 1 deletion(-) create mode 100644 tests/qemu/qemu-boot.c diff --git a/.gitignore b/.gitignore index e9170f427..5c614d88d 100644 --- a/.gitignore +++ b/.gitignore @@ -529,6 +529,7 @@ Makefile.in /tests/mountable/test-internal-parse-mountable /tests/parallel/test-parallel /tests/protocol/test-error-messages +/tests/qemu/qemu-boot /tests/regressions/rhbz501893 /tests/regressions/rhbz790721 /tests/regressions/rhbz914931 diff --git a/tests/qemu/Makefile.am b/tests/qemu/Makefile.am index 40237cab1..fa2fe92fe 100644 --- a/tests/qemu/Makefile.am +++ b/tests/qemu/Makefile.am @@ -29,4 +29,29 @@ TESTS = \ TESTS_ENVIRONMENT = $(top_builddir)/run --test EXTRA_DIST = \ - $(TESTS) + $(TESTS) \ + qemu-boot.c + +# qemu-boot is built but not run by default as it is mainly +# a qemu & kernel diagnostic tool. + +check_PROGRAMS = qemu-boot + +qemu_boot_SOURCES = \ + ../../df/estimate-max-threads.c \ + ../../df/estimate-max-threads.h \ + qemu-boot.c +qemu_boot_CPPFLAGS = \ + -DGUESTFS_PRIVATE=1 \ + -I$(top_srcdir)/gnulib/lib -I$(top_builddir)/gnulib/lib \ + -I$(top_srcdir)/src -I$(top_builddir)/src \ + -I$(top_srcdir)/df +qemu_boot_CFLAGS = \ + -pthread \ + $(WARN_CFLAGS) $(WERROR_CFLAGS) +qemu_boot_LDADD = \ + $(top_builddir)/src/libutils.la \ + $(top_builddir)/src/libguestfs.la \ + $(LIBXML2_LIBS) \ + $(LIBVIRT_LIBS) \ + $(top_builddir)/gnulib/lib/libgnu.la diff --git a/tests/qemu/qemu-boot.c b/tests/qemu/qemu-boot.c new file mode 100644 index 000000000..56eec57c7 --- /dev/null +++ b/tests/qemu/qemu-boot.c @@ -0,0 +1,270 @@ +/* libguestfs + * Copyright (C) 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. + */ + +/* Ancient libguestfs had a script called test-bootbootboot which just + * booted up the appliance in a loop. This was necessary back in the + * bad old days when qemu was not very reliable. This is the + * spiritual successor of that script, designed to find bugs in + * aarch64 KVM. You can control the number of boots that are done and + * the amount of parallelism. + */ + +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "guestfs.h" +#include "guestfs-internal-frontend.h" +#include "estimate-max-threads.h" + +#define MIN(a,b) ((a)<(b)?(a):(b)) + +/* Maximum number of threads we would ever run. Note this should not + * be > 20, unless libvirt is modified to increase the maximum number + * of clients. User can override this limit using -P. + */ +#define MAX_THREADS 12 + +static size_t n; /* Number of qemu processes to run in total. */ +static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; + +static int ignore_errors = 0; +static int trace = 0; +static int verbose = 0; + +struct thread_data { + int thread_num; + int r; +}; + +static void *start_thread (void *thread_data_vp); + +static void +usage (int exitcode) +{ + fprintf (stderr, + "qemu-boot: A program for repeatedly running the libguestfs appliance.\n" + "qemu-boot [-i] [-P ] -n \n" + " -i Ignore errors\n" + " -P Set number of parallel threads\n" + " (default is based on the amount of free memory)\n" + " -n Set number of appliances to run before exiting\n" + " -v Verbose appliance\n" + " -x Enable libguestfs tracing\n"); + exit (exitcode); +} + +int +main (int argc, char *argv[]) +{ + enum { HELP_OPTION = CHAR_MAX + 1 }; + static const char *options = "in:P:vx"; + static const struct option long_options[] = { + { "help", 0, 0, HELP_OPTION }, + { "ignore", 0, 0, 'i' }, + { "number", 1, 0, 'n' }, + { "processes", 1, 0, 'P' }, + { "trace", 0, 0, 'x' }, + { "verbose", 0, 0, 'v' }, + { 0, 0, 0, 0 } + }; + size_t P = 0, i, errors; + int c, option_index; + int err; + void *status; + + for (;;) { + c = getopt_long (argc, argv, options, long_options, &option_index); + if (c == -1) break; + + switch (c) { + case 0: + /* Options which are long only. */ + fprintf (stderr, "%s: unknown long option: %s (%d)\n", + program_name, long_options[option_index].name, option_index); + exit (EXIT_FAILURE); + + case 'i': + ignore_errors = 1; + break; + + case 'n': + if (sscanf (optarg, "%zu", &n) != 1 || n == 0) { + fprintf (stderr, "%s: -n option not numeric and greater than 0\n", + program_name); + exit (EXIT_FAILURE); + } + break; + + case 'P': + if (sscanf (optarg, "%zu", &P) != 1) { + fprintf (stderr, "%s: -P option not numeric\n", program_name); + exit (EXIT_FAILURE); + } + break; + + case 'v': + verbose = 1; + break; + + case 'x': + trace = 1; + break; + + case HELP_OPTION: + usage (EXIT_SUCCESS); + + default: + usage (EXIT_FAILURE); + } + } + + if (n == 0) { + fprintf (stderr, + "%s: must specify number of processes to run (-n option)\n", + program_name); + exit (EXIT_FAILURE); + } + + /* Calculate the number of threads to use. */ + if (P > 0) + P = MIN (n, P); + else + P = MIN (n, MIN (MAX_THREADS, estimate_max_threads ())); + + /* Start the worker threads. */ + struct thread_data thread_data[P]; + pthread_t threads[P]; + + for (i = 0; i < P; ++i) { + thread_data[i].thread_num = i; + err = pthread_create (&threads[i], NULL, start_thread, &thread_data[i]); + if (err != 0) { + fprintf (stderr, "%s: pthread_create[%zu]: %s\n", + program_name, i, strerror (err)); + exit (EXIT_FAILURE); + } + } + + /* Wait for the threads to exit. */ + errors = 0; + for (i = 0; i < P; ++i) { + err = pthread_join (threads[i], &status); + if (err != 0) { + fprintf (stderr, "%s: pthread_join[%zu]: %s\n", + program_name, i, strerror (err)); + errors++; + } + if (*(int *)status == -1) + errors++; + } + + exit (errors == 0 ? EXIT_SUCCESS : EXIT_FAILURE); +} + +/* Worker thread. */ +static void * +start_thread (void *thread_data_vp) +{ + struct thread_data *thread_data = thread_data_vp; + int quit = 0; + int err; + size_t i; + guestfs_h *g; + unsigned errors = 0; + + for (;;) { + /* Take the next process. */ + err = pthread_mutex_lock (&mutex); + if (err != 0) { + fprintf (stderr, "%s: pthread_mutex_lock: %s", + program_name, strerror (err)); + goto error; + } + + i = n; + if (i > 0) { + printf ("%zu to go ... \r", n); + fflush (stdout); + + n--; + } + else + quit = 1; + + err = pthread_mutex_unlock (&mutex); + if (err != 0) { + fprintf (stderr, "%s: pthread_mutex_unlock: %s", + program_name, strerror (err)); + goto error; + } + + if (quit) /* Work finished. */ + break; + + g = guestfs_create (); + if (g == NULL) { + perror ("guestfs_create"); + errors++; + if (!ignore_errors) + goto error; + } + + guestfs_set_trace (g, trace); + guestfs_set_verbose (g, verbose); + + if (guestfs_add_drive_ro (g, "/dev/null") == -1) { + errors++; + if (!ignore_errors) + goto error; + } + + if (guestfs_launch (g) == -1) { + errors++; + if (!ignore_errors) + goto error; + } + + if (guestfs_shutdown (g) == -1) { + errors++; + if (!ignore_errors) + goto error; + } + + guestfs_close (g); + } + + if (errors > 0) { + fprintf (stderr, "%s: thread %d: %u errors were ignored\n", + program_name, thread_data->thread_num, errors); + goto error; + } + + thread_data->r = 0; + return &thread_data->r; + + error: + thread_data->r = -1; + return &thread_data->r; +}