qemu-devel
[Top][All Lists]
Advanced

[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




reply via email to

[Prev in Thread] Current Thread [Next in Thread]