/* gcc -Wall test-parallel.c -o test-parallel -lvirt -lpthread */ #define _GNU_SOURCE #include #include #include #include #include #include #include #include #include #include #include #include #include #define TMPDIR "/tmp" /* You have to download these from * http://libguestfs.org/download/binaries/appliance/appliance-1.18.9.tar.xz * and unpack in TMPDIR. */ #define KERNEL TMPDIR "/appliance/kernel" #define INITRD TMPDIR "/appliance/initrd" #define APPLIANCE TMPDIR "/appliance/root" #define NR_THREADS 8 #define DEBUG 1 #define NOT_ROOT 1 #define LIBVIRT_URI "qemu:///session" #define UNIX_PATH_MAX 108 #define ROOT_DEV "sda" struct thread_state { pthread_t thread; /* Thread handle. */ size_t thread_num; /* Thread number. */ int exit_status; /* Thread exit status. */ }; static struct thread_state threads[NR_THREADS]; static void *start_thread (void *) __attribute__((noreturn)); static volatile sig_atomic_t quit = 0; static void catch_sigint (int signal) { static char cleaning_up[] = "\ngot signal, cleaning up ...\n"; if (quit == 0) { quit = 1; write (2, cleaning_up, sizeof cleaning_up); } } int main (int argc, char *argv[]) { struct sigaction sa; int r; size_t i, errors = 0; void *status; #if NOT_ROOT if (geteuid () == 0) { fprintf (stderr, "don't run this as root!\n"); exit (EXIT_FAILURE); } #endif srandom (time (NULL)); virInitialize (); memset (&sa, 0, sizeof sa); sa.sa_handler = catch_sigint; sa.sa_flags = SA_RESTART; sigaction (SIGINT, &sa, NULL); sigaction (SIGQUIT, &sa, NULL); for (i = 0; i < NR_THREADS; ++i) { threads[i].thread_num = i; /* Start the thread. */ r = pthread_create (&threads[i].thread, NULL, start_thread, &threads[i]); if (r != 0) error (EXIT_FAILURE, r, "pthread_create"); } /* Wait for the threads to exit. */ for (i = 0; i < NR_THREADS; ++i) { r = pthread_join (threads[i].thread, &status); if (r != 0) error (EXIT_FAILURE, r, "pthread_join"); if (*(int *)status != 0) { fprintf (stderr, "%zu: thread returned an error\n", i); errors++; } } exit (errors == 0 ? EXIT_SUCCESS : EXIT_FAILURE); } /* Run the test in a single thread. */ static void * start_thread (void *statevp) { struct thread_state *state = statevp; size_t i = 0; int r, fd, fd2; struct sockaddr_un addr; struct sockaddr addr2; char snapshot[256]; char sock[256]; char cmd[256]; char xml[2048]; virConnectPtr conn; virDomainPtr dom; char flag[4]; socklen_t addr_size; snprintf (snapshot, sizeof snapshot, TMPDIR "/snapshot%zu.qcow2", state->thread_num); snprintf (sock, sizeof sock, TMPDIR "/sock%zu", state->thread_num); /* Create the virtio socket. */ unlink (sock); fd = socket (AF_UNIX, SOCK_STREAM|SOCK_CLOEXEC, 0); if (fd == -1) { perror ("socket"); goto error; } addr.sun_family = AF_UNIX; strncpy (addr.sun_path, sock, UNIX_PATH_MAX); addr.sun_path[UNIX_PATH_MAX-1] = '\0'; if (bind (fd, (struct sockaddr *) &addr, sizeof addr) == -1) { perror ("bind"); goto error; } if (listen (fd, 1) == -1) { perror ("listen"); goto error; } while (!quit) { i++; /* Create a snapshot of the appliance disk. */ snprintf (cmd, sizeof cmd, "rm -f %s; " "qemu-img create -f qcow2 -b %s -o backing_fmt=raw %s" " >/dev/null 2>&1", snapshot, APPLIANCE, snapshot); if (system (cmd) != 0) { fprintf (stderr, "command failed: %s\n", cmd); goto error; } snprintf (xml, sizeof xml, "\n" "\n" " test_%zu_%zu\n" " 500\n" " 500\n" " 1\n" " \n" " \n" " hvm\n" " %s\n" " %s\n" " panic=1 console=ttyS0 udevtimeout=600 no_timer_check acpi=off printk.time=1 cgroup_disable=memory root=/dev/" ROOT_DEV " selinux=0 TERM=xterm-256color\n" " \n" " destroy\n" " \n" " \n" " \n" " \n" " \n" " \n" "
\n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" "", state->thread_num, i, KERNEL, INITRD, snapshot, sock); /* Create the libvirt transient domain. */ #if DEBUG printf ("%zu: opening libvirt\n", state->thread_num); #endif conn = virConnectOpen (LIBVIRT_URI); if (!conn) goto error; #if DEBUG printf ("%zu: starting domain\n", state->thread_num); #endif dom = virDomainCreateXML (conn, xml, VIR_DOMAIN_START_AUTODESTROY); if (!dom) goto error; /* Wait for the launch flag to be received, which indicates that * the daemon is running. */ again: #if DEBUG printf ("%zu: waiting for connection back from daemon\n", state->thread_num); #endif addr_size = sizeof addr2; fd2 = accept4 (fd, (struct sockaddr *) &addr2, &addr_size, SOCK_CLOEXEC); if (fd2 == -1) { if (errno == EAGAIN || errno == EWOULDBLOCK) goto again; perror ("accept4"); goto error; } r = read (fd2, flag, 4); if (r == -1) { perror ("read"); goto error; } if (r != 4) { fprintf (stderr, "could not read launch flag: probably the transient domain failed to launch\n"); goto error; } close (fd2); /* Destroy the domain. */ #if DEBUG printf ("%zu: destroying domain\n", state->thread_num); #endif if (virDomainDestroyFlags (dom, VIR_DOMAIN_DESTROY_GRACEFUL) == -1) goto error; virDomainFree (dom); virConnectClose (conn); } close (fd); /* Test finished successfully. */ fprintf (stderr, "%zu: thread exiting successfully\n", state->thread_num); state->exit_status = 0; pthread_exit (&state->exit_status); /* Test failed. */ error: fprintf (stderr, "%zu: thread exiting on error\n", state->thread_num); state->exit_status = 1; pthread_exit (&state->exit_status); }