[Top][All Lists]
[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
[Qemu-devel] [PATCH 2/2] [FYI] coverage test for Linux installs
From: |
Anthony Liguori |
Subject: |
[Qemu-devel] [PATCH 2/2] [FYI] coverage test for Linux installs |
Date: |
Mon, 8 Aug 2011 14:31:38 -0500 |
This is a simple tool that I've been using for the past couple months to help
with day-to-day testing of changes. It may seem like it's similar to
kvm-autotest but it's actually quite different. Most noticably:
- It's a coverage test instead of an acceptance test. Each time it runs it
uses a slightly a semi-random configuration for the guest. This means that
the more you run it, the more coverage you get. This is a good fit for
maintainer testing because you get wide testing without having to wait for
48-hour test cycles.
- It runs in the foreground as an unprivileged user. This means I can kick it
off in a terminal while I go off and do something else, but still keep an
eye on what's going on.
- It works with TCG guests too and includes a TCG test case for the pseries
Power machine.
That said, it's *not* a replacement for KVM autotest. It is not a good tool
for doing acceptance testing, like you would do to cut a new QEMU release. But
perhaps there's some behavior that could be leveraged by KVM autotest in the
future here.
I'm not proposing this for tree inclusion right now. Just sharing a tool that
I've found to be useful. I really just want the previous patch to go in so that
I can stop carrying that patch privately.
Right now, you need to setup an ISO directory to use the test tool. After
copy-on-read lands in the tree, I plan on making it create CoR files backing to
http so that no explicit setup is required.
---
test-linux.c | 549 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
1 files changed, 549 insertions(+), 0 deletions(-)
create mode 100644 test-linux.c
diff --git a/test-linux.c b/test-linux.c
new file mode 100644
index 0000000..55bcaa0
--- /dev/null
+++ b/test-linux.c
@@ -0,0 +1,549 @@
+/*
+ */
+
+#include "qemu-common.h"
+#include "qemu_socket.h"
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <unistd.h>
+#include <glib.h>
+#include <linux/iso_fs.h>
+
+static uint8_t iso_u8(void *ptr)
+{
+ uint8_t *buf = (uint8_t *)ptr;
+ return buf[0];
+}
+
+static inline uint16_t iso_u16(void *ptr)
+{
+ uint8_t *buf = (uint8_t *)ptr;
+ return buf[0] | (buf[1] << 8);
+}
+
+static inline uint32_t iso_u32(void *ptr)
+{
+ uint8_t *buf = (uint8_t *)ptr;
+ return (buf[3] << 24) | (buf[2] << 16) | (buf[1] << 8) | buf[0];
+}
+
+typedef struct IsoFile
+{
+ int directory;
+ off_t offset;
+ off_t size;
+} IsoFile;
+
+static IsoFile *iso_find_path(int fd, IsoFile *dir, const char *pathname)
+{
+ struct iso_directory_record record;
+ ssize_t len;
+ char name[257];
+ uint8_t name_len;
+ off_t offset = dir->offset;
+
+ if (!dir->directory) {
+ return NULL;
+ }
+
+ len = pread(fd, &record, sizeof(record), offset);
+ assert(len == sizeof(record));
+
+ while ((offset - dir->offset) < dir->size) {
+ size_t size;
+ int directory;
+
+ name_len = iso_u8(record.name_len);
+ directory = !!(iso_u8(record.flags) & 0x02);
+
+ size = len;
+ len = pread(fd, name, name_len, offset + len);
+ assert(len == name_len);
+ name[name_len] = 0;
+
+ size += len;
+
+ if (size < iso_u8(record.length)) {
+ size_t record_length = iso_u8(record.length) - size;
+ char buffer[256];
+ int i = 0;
+
+ len = pread(fd, buffer, record_length, offset + size);
+ assert(len == record_length);
+ if (buffer[i] == 0) {
+ i++;
+ }
+
+ if (record_length > 5 && buffer[i] == 'R' && buffer[i + 1] == 'R')
{
+ i += 5;
+ while (i < record_length) {
+ if (buffer[i] == 'N' && buffer[i + 1] == 'M') {
+ name_len = buffer[i + 2] - 5;
+ memcpy(name, &buffer[i + 5], name_len);
+ name[name_len] = 0;
+ i += buffer[i + 2];
+ } else {
+ break;
+ }
+ }
+ }
+ }
+
+ if (strcmp(name, pathname) == 0) {
+ IsoFile *ret = malloc(sizeof(*ret));
+ ret->directory = directory;
+ ret->offset = iso_u32(record.extent) * 2048;
+ ret->size = iso_u32(record.size);
+ return ret;
+ }
+
+ offset += iso_u8(record.length);
+
+ do {
+ len = pread(fd, &record, sizeof(record), offset);
+ assert(len == sizeof(record));
+
+ if (iso_u8(record.length) == 0) {
+ offset += 2047;
+ offset &= ~2047ULL;
+ }
+ } while (iso_u8(record.length) == 0 && (offset - dir->offset) <
dir->size);
+ }
+
+ return NULL;
+}
+
+static IsoFile *iso_find_root(int fd)
+{
+ off_t offset = 0;
+ ssize_t len;
+ struct iso_volume_descriptor vd;
+
+ offset = 32768;
+ do {
+ len = pread(fd, &vd, sizeof(vd), offset);
+ assert(len == sizeof(vd));
+
+ if (iso_u8(vd.type) == 1) {
+ struct iso_primary_descriptor *pd;
+ struct iso_directory_record *root;
+ char name[256];
+ IsoFile *ret;
+
+ pd = (struct iso_primary_descriptor *)&vd;
+ root = (struct iso_directory_record *)pd->root_directory_record;
+
+ memcpy(name, root->name, iso_u8(root->name_len));
+ name[iso_u8(root->name_len)] = 0;
+
+ ret = malloc(sizeof(*ret));
+ ret->directory = 1;
+ ret->offset = iso_u32(root->extent) * 2048;
+ ret->size = iso_u32(root->size);
+ return ret;
+ }
+
+ offset += len;
+ } while (iso_u8(vd.type) != 255);
+
+ return NULL;
+}
+
+static IsoFile *iso_find_file(int fd, const char *filename)
+{
+ const char *end;
+ IsoFile *cur = iso_find_root(fd);
+
+ while (cur && filename) {
+ IsoFile *dir;
+ char pathname[257];
+
+ end = strchr(filename, '/');
+ if (end) {
+ memcpy(pathname, filename, end - filename);
+ pathname[end - filename] = 0;
+ filename = end + 1;
+ } else {
+ snprintf(pathname, sizeof(pathname), "%s", filename);
+ filename = end;
+ }
+
+ dir = iso_find_path(fd, cur, pathname);
+ free(cur);
+ cur = dir;
+ }
+
+ return cur;
+}
+
+static const char preseed[] =
+ "d-i debian-installer/locale string en_US.UTF-8\n"
+ "d-i debian-installer/splash boolean false\n"
+ "d-i console-setup/ask_detect boolean false\n"
+ "d-i console-setup/layoutcode string us\n"
+ "d-i console-setup/variantcode string \n"
+ "d-i netcfg/get_nameservers string \n"
+ "d-i netcfg/get_ipaddress string \n"
+ "d-i netcfg/get_netmask string 255.255.255.0\n"
+ "d-i netcfg/get_gateway string \n"
+ "d-i netcfg/confirm_static boolean true\n"
+ "d-i clock-setup/utc boolean true\n"
+ "d-i partman-auto/method string regular\n"
+ "d-i partman-lvm/device_remove_lvm boolean true\n"
+ "d-i partman-lvm/confirm boolean true\n"
+ "d-i partman/confirm_write_new_label boolean true\n"
+ "d-i partman/choose_partition select Finish partitioning and
write changes to disk\n"
+ "d-i partman/confirm boolean true\n"
+ "d-i partman/confirm_nooverwrite boolean true\n"
+ "d-i partman/default_filesystem string ext3\n"
+ "d-i clock-setup/utc boolean true\n"
+ "d-i clock-setup/ntp boolean true\n"
+ "d-i clock-setup/ntp-server string ntp.ubuntu.com\n"
+ "d-i base-installer/kernel/image string linux-server\n"
+ "d-i passwd/root-login boolean false\n"
+ "d-i passwd/make-user boolean true\n"
+ "d-i passwd/user-fullname string ubuntu\n"
+ "d-i passwd/username string ubuntu\n"
+ "d-i passwd/user-password-crypted password
$6$.1eHH0iY$ArGzKX2YeQ3G6U.mlOO3A.NaL22Ewgz8Fi4qqz.Ns7EMKjEJRIW2Pm/TikDptZpuu7I92frytmk5YeL.9fRY4.\n"
+ "d-i passwd/user-uid string \n"
+ "d-i user-setup/allow-password-weak boolean false\n"
+ "d-i user-setup/encrypt-home boolean false\n"
+ "d-i passwd/user-default-groups string adm cdrom dialout
lpadmin plugdev sambashare\n"
+ "d-i apt-setup/services-select multiselect security\n"
+ "d-i apt-setup/security_host string security.ubuntu.com\n"
+ "d-i apt-setup/security_path string /ubuntu\n"
+ "d-i debian-installer/allow_unauthenticated string false\n"
+ "d-i pkgsel/upgrade select safe-upgrade\n"
+ "d-i pkgsel/language-packs multiselect \n"
+ "d-i pkgsel/update-policy select none\n"
+ "d-i pkgsel/updatedb boolean true\n"
+ "d-i grub-installer/skip boolean false\n"
+ "d-i lilo-installer/skip boolean false\n"
+ "d-i grub-installer/only_debian boolean true\n"
+ "d-i grub-installer/with_other_os boolean true\n"
+ "d-i finish-install/keep-consoles boolean false\n"
+ "d-i finish-install/reboot_in_progress note \n"
+ "d-i cdrom-detect/eject boolean true\n"
+ "d-i debian-installer/exit/halt boolean false\n"
+ "d-i debian-installer/exit/poweroff boolean false\n"
+ "d-i preseed/late_command string echo -ne '\x1' | dd bs=1
count=1 seek=1281 of=/dev/port\n"
+ ;
+
+static const char ks[] =
+ "install\n"
+ "text\n"
+ "reboot\n"
+ "lang en_US.UTF-8\n"
+ "keyboard us\n"
+ "network --bootproto dhcp\n"
+ "rootpw 123456\n"
+ "firewall --enabled --ssh\n"
+ "selinux --enforcing\n"
+ "timezone --utc America/New_York\n"
+ "firstboot --disable\n"
+ "bootloader --location=mbr --append=\"console=tty0
console=ttyS0,115200\"\n"
+ "zerombr\n"
+ "clearpart --all --initlabel\n"
+ "autopart\n"
+ "poweroff\n"
+ "\n"
+ "%packages\n"
+ "@base\n"
+ "@core\n"
+ "%end\n"
+ "%post\n"
+ "echo -ne '\x1' | dd bs=1 count=1 seek=1281 of=/dev/port\n"
+ "%end\n"
+ ;
+
+static int systemf(const char *fmt, ...)
+ __attribute__((format(printf, 1, 2)));
+
+static int systemf(const char *fmt, ...)
+{
+ char buffer[1024];
+ va_list ap;
+
+ va_start(ap, fmt);
+ vsnprintf(buffer, sizeof(buffer), fmt, ap);
+ va_end(ap);
+
+ g_test_message("Running command %s\n", buffer);
+
+ return system(buffer);
+}
+
+typedef struct WeightedChoice
+{
+ const char *string;
+ int percentage;
+} WeightedChoice;
+
+static const char *choose(WeightedChoice *choices)
+{
+ int i, value;
+ int cur_percentage = 0;
+
+ value = g_test_rand_int_range(0, 100);
+ for (i = 0; choices[i].string; i++) {
+ cur_percentage += choices[i].percentage;
+ if (value < cur_percentage) {
+ return choices[i].string;
+ }
+ }
+
+ g_assert_not_reached();
+ return NULL;
+}
+
+static void copy_file_from_iso(const char *iso, const char *src, const char
*dst)
+{
+ int iso_fd, dst_fd;
+ IsoFile *filp;
+ off_t offset;
+
+ iso_fd = open(iso, O_RDONLY);
+ dst_fd = open(dst, O_RDWR | O_CREAT | O_TRUNC, 0644);
+
+ g_assert(iso_fd != -1);
+ g_assert(dst_fd != -1);
+
+ filp = iso_find_file(iso_fd, src);
+
+ for (offset = 0; offset < filp->size; offset += 2048) {
+ char buffer[2048];
+ size_t size = MIN(filp->size - offset, 2048);
+ ssize_t len;
+
+ len = pread(iso_fd, buffer, size, filp->offset + offset);
+ g_assert(len == size);
+
+ len = pwrite(dst_fd, buffer, size, offset);
+ g_assert(len == size);
+ }
+
+ close(dst_fd);
+ close(iso_fd);
+}
+
+static void test_image(const char *command,
+ const char *image, const char *iso,
+ const char *kernel, const char *initrd,
+ const char *cmdline, const char *config_file,
+ bool pseries)
+{
+ char buffer[1024];
+ int fd;
+ pid_t pid;
+ int status, ret;
+ long max_cpus, max_mem;
+ int num_cpus, mem_mb;
+ struct sockaddr_un addr;
+ const char *tmp_kernel = "/tmp/test-vmlinuz";
+ const char *tmp_initrd = "/tmp/test-initrd";
+ const char *tmp_sock = "/tmp/test-linux.sock";
+ const char *nic_type, *blk_type, *cache_type, *disk_format, *aio_method;
+ const char *vga_type;
+ int connect_count;
+ WeightedChoice nic_types[] = {
+ { "e1000", 25 },
+ { "virtio", 50 },
+ { "rtl8139", 25 },
+ { }
+ };
+ WeightedChoice blk_types[] = {
+ { "virtio", 50 },
+ { "ide", 50 },
+ { }
+ };
+ WeightedChoice cache_types[] = {
+ { "none", 50 },
+ { "writethrough", 50 },
+ { }
+ };
+ WeightedChoice disk_formats[] = {
+ { "raw", 50 },
+ { "qcow2", 25 },
+ { "qed", 25 },
+ { }
+ };
+ WeightedChoice aio_methods[] = {
+ { "threads", 75 },
+ { "native", 25 },
+ { }
+ };
+ WeightedChoice vga_types[] = {
+ { "cirrus", 80 },
+ { "std", 20 },
+ { }
+ };
+
+ max_cpus = sysconf(_SC_NPROCESSORS_ONLN);
+ num_cpus = MIN(g_test_rand_int_range(1, max_cpus + 1), 8);
+
+ /* Don't use more than 1/2 of physical memory */
+ max_mem = (sysconf(_SC_PHYS_PAGES) * sysconf(_SC_PAGE_SIZE)) >> 20;
+ max_mem /= 2;
+
+ mem_mb = (g_test_rand_int_range(128, max_mem) + 7) & ~0x03;
+
+ nic_type = choose(nic_types);
+ blk_type = choose(blk_types);
+ cache_type = choose(cache_types);
+ disk_format = choose(disk_formats);
+ aio_method = choose(aio_methods);
+ vga_type = choose(vga_types);
+
+ if (pseries) {
+ nic_type = "ibmveth";
+ blk_type = "scsi";
+ vga_type = "none";
+ }
+
+ if (strcmp(cache_type, "none") != 0) {
+ aio_method = "threads";
+ }
+
+ g_test_message("Using %d VCPUS", num_cpus);
+ g_test_message("Using %d MB of RAM", mem_mb);
+ g_test_message("Using `%s' network card", nic_type);
+ g_test_message("Using `%s' block device, cache=`%s', format=`%s',
aio=`%s'",
+ blk_type, cache_type, disk_format, aio_method);
+ g_test_message("Using `%s' graphics card", vga_type);
+
+ copy_file_from_iso(iso, kernel, tmp_kernel);
+ copy_file_from_iso(iso, initrd, tmp_initrd);
+
+ pid = fork();
+ if (pid == 0) {
+ int status;
+
+ status = systemf("./qemu-img create -f %s %s 10G", disk_format, image);
+ if (status != 0) {
+ exit(WEXITSTATUS(status));
+ }
+
+ status = systemf("%s "
+ "-drive file=%s,if=%s,cache=%s,aio=%s -cdrom %s "
+ "-chardev socket,server=on,wait=on,id=httpd,path=%s "
+ "-net user,guestfwd=tcp:10.0.2.1:80-chardev:httpd "
+ "-net nic,model=%s "
+ "-kernel %s -initrd %s "
+ "-append '%s' -vga %s "
+ "-serial stdio -vnc none -smp %d -m %d ",
+ command, image, blk_type, cache_type, aio_method,
+ iso, tmp_sock, nic_type, tmp_kernel, tmp_initrd,
+ cmdline, vga_type, num_cpus, mem_mb);
+ unlink(image);
+
+ if (!WIFEXITED(status)) {
+ exit(1);
+ }
+
+ exit(WEXITSTATUS(status));
+ }
+
+ fd = socket(PF_UNIX, SOCK_STREAM, 0);
+ g_assert(fd != -1);
+
+ addr.sun_family = AF_UNIX;
+ snprintf(addr.sun_path, sizeof(addr.sun_path), "%s", tmp_sock);
+
+ connect_count = 0;
+ do {
+ ret = connect(fd, (struct sockaddr *)&addr, sizeof(addr));
+ if (ret == -1) {
+ usleep(100000);
+ }
+ connect_count++;
+ } while (ret == -1 && connect_count < 100);
+
+ g_assert(connect_count < 100);
+
+ ret = read(fd, buffer, 1);
+ g_assert(ret == 1);
+ snprintf(buffer, sizeof(buffer),
+ "HTTP/1.0 200 OK\r\n"
+ "Server: BaseHTTP/0.3 Python/2.6.5\r\n"
+ "Date: Wed, 30 Mar 2011 19:46:35 GMT\r\n"
+ "Content-type: text/plain\r\n"
+ "Content-length: %ld\r\n"
+ "\r\n", strlen(config_file));
+ ret = write(fd, buffer, strlen(buffer));
+ g_assert_cmpint(ret, ==, strlen(buffer));
+
+ ret = write(fd, config_file, strlen(config_file));
+ g_assert_cmpint(ret, ==, strlen(config_file));
+
+ ret = waitpid(pid, &status, 0);
+ g_assert(ret == pid);
+ g_assert(WIFEXITED(status));
+ g_assert_cmpint(WEXITSTATUS(status), ==, 3);
+
+ close(fd);
+}
+
+static void test_debian_ppc(gconstpointer data)
+{
+ const char *distro = data;
+ char image[1024];
+ char iso[1024];
+
+ snprintf(image, sizeof(image), "/tmp/debian-%s.img", distro);
+ snprintf(iso, sizeof(iso), "%s/isos/debian-%s-DVD-1.iso",
+ getenv("HOME"), distro);
+
+ test_image("ppc64-softmmu/qemu-system-ppc64 -M pseries ",
+ image, iso, "/install/powerpc64/vmlinux",
+ "/install/powerpc64/initrd.gz",
+ "priority=critical locale=en_US "
+ "url=http://10.0.2.1/server.cfg console=hvc0",
+ preseed, true);
+}
+
+static void test_ubuntu(gconstpointer data)
+{
+ const char *distro = data;
+ char image[1024];
+ char iso[1024];
+
+ snprintf(image, sizeof(image), "/tmp/ubuntu-%s.img", distro);
+ snprintf(iso, sizeof(iso), "%s/isos/ubuntu-%s.iso", getenv("HOME"),
distro);
+
+ test_image("x86_64-softmmu/qemu-system-x86_64 -enable-kvm",
+ image, iso, "/install/vmlinuz", "/install/initrd.gz",
+ "priority=critical locale=en_US "
+ "url=http://10.0.2.1/server.cfg console=ttyS0",
+ preseed, false);
+}
+
+static void test_fedora(gconstpointer data)
+{
+ const char *distro = data;
+ char image[1024];
+ char iso[1024];
+
+ snprintf(image, sizeof(image), "/tmp/fedora-%s.img", distro);
+ snprintf(iso, sizeof(iso), "%s/isos/Fedora-%s-DVD.iso", getenv("HOME"),
distro);
+
+ test_image("x86_64-softmmu/qemu-system-x86_64 -enable-kvm",
+ image, iso, "/isolinux/vmlinuz", "/isolinux/initrd.img",
+ "stage2=hd:LABEL=\"Fedora\" "
+ "ks=http://10.0.2.1/server.ks console=ttyS0",
+ ks, false);
+}
+
+int main(int argc, char **argv)
+{
+ g_test_init(&argc, &argv, NULL);
+
+ g_test_add_data_func("/fedora/14/x86_64", "14-x86_64", test_fedora);
+ g_test_add_data_func("/ubuntu/9.10/server/amd64", "9.10-server-amd64",
test_ubuntu);
+ g_test_add_data_func("/ubuntu/10.04.2/server/amd64",
"10.04.2-server-amd64", test_ubuntu);
+ g_test_add_data_func("/ubuntu/10.10/server/amd64", "10.10-server-amd64",
test_ubuntu);
+ g_test_add_data_func("/debian/6.0.1a/powerpc", "6.0.1a-powerpc",
test_debian_ppc);
+
+ g_test_run();
+
+ return 0;
+}
--
1.7.4.1