The attached patch adds a new driver which provides a 'mock' hypervisor connection. The idea behind this is to provide a completely predictable and isolated hypervisor implementation, which will facilitate creation of unit tests in applications using libvirt. The code attached is not a complete implementation, providing support only for connecting, listing domains, getting a domain by id/uuid/name, getting domain info, getting node info. Further work will hook up methods for creating domains, exporting XML & the other various libvirt APIs. The driver is intended to operate as a black box, closed system - the only interaction an application will have is via the libVirt entry points. It starts off with a single domain present, and fixed node info, and updates domain CPU time based on elapsed wall clock time. When the domain create API is hooked up this will let apps create new dummy domains within the context of the process. As I said, I delibrately implemented this driver to only have process local state. When the process shuts down all state is discarded. This keeps application usage very simple. Longer term I think it would be useful to have a 2nd, dynamic, which would allow fault injection, cross-process interaction & more to allow advanced scripting of a test scenario. I don't have time to create such a driver myself though, so I stuck with the simplest possible impl which will allow 95% of application unit testing needs to be satisfied. To use this driver, simply pass 'TestSimple' as the name parameter to virConnectOpen / virConnectOpenReadonly instead of NULL. If you are using 'virsh' then my previous patch will let you call 'virsh --connect TestSimple' I've also tested this with the 'gnome-vm-applet' panel applet. The only problem I have found is that the 'xend_internal.c' driver will always return success from its 'xenDaemonOpen' method, regardless of whether there is actually a Xen Daemon present. So when using the test driver, every method will first try to ue the XenD driver impl, fail (printing an error message) and then go onto use the test driver. The xenDaemonOpen method really needs to be fixed to only succeed when Xen is actually present. Or perhaps it should only try to run when the 'name' passed to virConnectOpen is NULL or 'Xen' - ie be a no op if the name is 'TestSimple' / QEMU / any other driver implementation. I'm not sure whether we want to commit this to the libvirt codebae just, since there is a fair bit more work to be done to hook up additional methods. Regards, Dan. -- |=- Red Hat, Engineering, Emerging Technologies, Boston. +1 978 392 2496 -=| |=- Perl modules: http://search.cpan.org/~danberr/ -=| |=- Projects: http://freshmeat.net/~danielpb/ -=| |=- GnuPG: 7D3B9505 F3C9 553F A1DA 4AC2 5648 23C1 B3DF F742 7D3B 9505 -=|
# HG changeset patch # User "Daniel P. Berrange <berrange@xxxxxxxxxx>" # Node ID 102f74a55c62aca010cda8893798e4a0516dd7f4 # Parent 5ec05a73e1fb1eeda431c0ddc7bd6ae1ce44812b Initial comment of test driver diff -r 5ec05a73e1fb -r 102f74a55c62 ChangeLog --- a/ChangeLog Fri May 26 12:03:10 2006 -0400 +++ b/ChangeLog Fri May 26 12:25:52 2006 -0400 @@ -1,3 +1,9 @@ Fri May 26 11:59:20 EDT 2006 Daniel P. B +Fri May 26 12:24:24 EDT 2006 Daniel P. Berrange <berrange@xxxxxxxxxx> + + * src/libvirt.c: Activate test harness driver + * src/Makefile.am, src/test_simple.c, src/test_simple.h: New driver + backend for use by application unit tests + Fri May 26 11:59:20 EDT 2006 Daniel P. Berrange <berrange@xxxxxxxxxx> * src/hash.c, src/internal.h: Switch the uuid parameter in virGetDomain diff -r 5ec05a73e1fb -r 102f74a55c62 src/Makefile.am --- a/src/Makefile.am Fri May 26 12:03:10 2006 -0400 +++ b/src/Makefile.am Fri May 26 12:25:52 2006 -0400 @@ -22,6 +22,8 @@ libvirt_la_SOURCES = \ xend_internal.c xend_internal.h \ sexpr.c sexpr.h \ virterror.c \ + test_simple.c \ + test_simple.h \ driver.h bin_PROGRAMS = virsh diff -r 5ec05a73e1fb -r 102f74a55c62 src/libvirt.c --- a/src/libvirt.c Fri May 26 12:03:10 2006 -0400 +++ b/src/libvirt.c Fri May 26 12:25:52 2006 -0400 @@ -28,6 +28,7 @@ #include "xen_internal.h" #include "xend_internal.h" #include "xs_internal.h" +#include "test_simple.h" #include "xml.h" /* @@ -70,6 +71,7 @@ virInitialize(void) xenHypervisorRegister(); xenDaemonRegister(); xenStoreRegister(); + testSimpleRegister(); return(0); } diff -r 5ec05a73e1fb -r 102f74a55c62 src/test_simple.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/test_simple.c Fri May 26 12:25:52 2006 -0400 @@ -0,0 +1,325 @@ +/* + * test.c: A "mock" hypervisor for use by application unit tests + * + * Copyright (C) 2006 Red Hat, Inc. + * + * See COPYING.LIB for the License of this software + * + * Daniel Berrange <berrange@xxxxxxxxxx> + */ + +#include <stdio.h> +#include <string.h> +#include <sys/time.h> + +#include "internal.h" +#include "test_simple.h" + +static virDriver testSimpleDriver = { + "TestSimple", + NULL, /* init */ + testSimpleOpen, /* open */ + testSimpleClose, /* close */ + NULL, /* type */ + testSimpleGetVersion, /* version */ + testSimpleNodeGetInfo, /* nodeGetInfo */ + testSimpleListDomains, /* listDomains */ + testSimpleNumOfDomains, /* numOfDomains */ + NULL, /* domainCreateLinux */ + testSimpleLookupDomainByID, /* domainLookupByID */ + testSimpleLookupDomainByUUID, /* domainLookupByUUID */ + testSimpleLookupDomainByName, /* domainLookupByName */ + testSimplePauseDomain, /* domainSuspend */ + testSimpleResumeDomain, /* domainResume */ + NULL, /* domainShutdown */ + NULL, /* domainReboot */ + testSimpleDestroyDomain, /* domainDestroy */ + NULL, /* domainFree */ + NULL, /* domainGetName */ + NULL, /* domainGetID */ + NULL, /* domainGetUUID */ + NULL, /* domainGetOSType */ + NULL, /* domainGetMaxMemory */ + testSimpleSetMaxMemory, /* domainSetMaxMemory */ + NULL, /* domainSetMemory */ + testSimpleGetDomainInfo, /* domainGetInfo */ + NULL, /* domainSave */ + NULL /* domainRestore */ +}; + +typedef struct _testSimpleDev { + char name[20]; + virDeviceMode mode; +} testSimpleDev; + +#define MAX_DEVICES 10 + +typedef struct _testSimpleDom { + int active; + char name[20]; + unsigned char uuid[16]; + virDomainKernel kernel; + virDomainInfo info; + virDomainRestart onRestart; + int numDevices; + testSimpleDev devices[MAX_DEVICES]; +} testSimpleDom; + +#define MAX_DOMAINS 20 + +typedef struct _testSimpleCon { + int active; + int numDomains; + testSimpleDom domains[MAX_DOMAINS]; +} testSimpleCon; + +#define MAX_CONNECTIONS 5 + +typedef struct _testSimpleNode { + int numConnections; + testSimpleCon connections[MAX_CONNECTIONS]; +} testSimpleNode; + +/* XXX, how about we stuff this in a SHM + segment so multiple apps can run tests + against the mock hypervisor concurrently. + Would need a pthread process shared mutex + too probably */ +static testSimpleNode node; + +static virNodeInfo nodeInfo = { + "t86", + 1024*1024*3, /* 3 GB */ + 16, + 1400, + 2, + 2, + 2, + 2, +}; + +static void +testError(virConnectPtr con, + virDomainPtr dom, + virErrorNumber error, + const char *info) +{ + const char *errmsg; + + if (error == VIR_ERR_OK) + return; + + errmsg = __virErrorMsg(error, info); + __virRaiseError(con, dom, VIR_FROM_XEN, error, VIR_ERR_ERROR, + errmsg, info, NULL, 0, 0, errmsg, info, 0); +} + + +/** + * testSimpleRegister: + * + * Registers the testSimple driver + */ +void testSimpleRegister(void) +{ + virRegisterDriver(&testSimpleDriver); + + memset(&node, 0, sizeof(node)); +} + + +int testSimpleOpen(virConnectPtr conn, + const char *name, + int flags) +{ + int i; + + if (!name || strcmp(name, testSimpleDriver.name)) { + return -1; + } + + for (i = 0 ; i < MAX_CONNECTIONS ; i++) { + if (!node.connections[i].active) { + struct timeval tv; + + if (gettimeofday(&tv, NULL) < 0) { + testError(NULL, NULL, VIR_ERR_INTERNAL_ERROR, "cannot get timeofday"); + return -1; + } + + conn->handle = i; + node.connections[i].active = 1; + + node.connections[0].numDomains = 1; + node.connections[0].domains[0].active = 1; + strcpy(node.connections[i].domains[0].name, "Domain-0"); + for (i = 0 ; i < 16 ; i++) { + node.connections[0].domains[0].uuid[i] = (i * 75)%255; + } + node.connections[0].domains[0].info.maxMem = 8192 * 1024; + node.connections[0].domains[0].info.memory = 2048 * 1024; + node.connections[0].domains[0].info.state = VIR_DOMAIN_RUNNING; + node.connections[0].domains[0].info.nrVirtCpu = 2; + node.connections[0].domains[0].info.cpuTime = ((tv.tv_sec * 1000ll *1000ll) + tv.tv_usec) / 2; + return 0; + } + } + + + testError(NULL, NULL, VIR_ERR_INTERNAL_ERROR, "too make connections"); + return -1; +} + +int testSimpleClose(virConnectPtr conn) +{ + testSimpleCon *con = &node.connections[conn->handle]; + con->active = 0; + conn->handle = -1; + memset(con, 0, sizeof(testSimpleCon)); + return 0; +} + +int testSimpleGetVersion(virConnectPtr conn, + unsigned long *hvVer) +{ + *hvVer = 1; + return 0; +} + +int testSimpleNodeGetInfo(virConnectPtr conn, + virNodeInfoPtr info) +{ + memcpy(info, &nodeInfo, sizeof(nodeInfo)); + return 0; +} + +int testSimpleNumOfDomains(virConnectPtr conn) +{ + testSimpleCon *con = &node.connections[conn->handle]; + return con->numDomains; +} + +virDomainPtr testSimpleLookupDomainByID(virConnectPtr conn, + int id) +{ + testSimpleCon *con = &node.connections[conn->handle]; + virDomainPtr dom; + if (!con->domains[id].active) { + return NULL; + } + dom = virGetDomain(conn, con->domains[id].name, con->domains[id].uuid); + if (dom == NULL) { + testError(conn, NULL, VIR_ERR_NO_MEMORY, "Allocating domain"); + return(NULL); + } + dom->handle = id; + return dom; +} + +virDomainPtr testSimpleLookupDomainByUUID(virConnectPtr conn, + const unsigned char *uuid) +{ + testSimpleCon *con = &node.connections[conn->handle]; + virDomainPtr dom = NULL; + int i, id = -1; + for (i = 0 ; i < MAX_DOMAINS ; i++) { + if (con->domains[i].active && + memcmp(uuid, con->domains[i].uuid, 16) == 0) { + id = i; + break; + } + } + if (id >= 0) { + dom = virGetDomain(conn, con->domains[id].name, con->domains[id].uuid); + if (dom == NULL) { + testError(conn, NULL, VIR_ERR_NO_MEMORY, "Allocating domain"); + return(NULL); + } + dom->handle = id; + } + return dom; +} + +virDomainPtr testSimpleLookupDomainByName(virConnectPtr conn, + const char *name) +{ + testSimpleCon *con = &node.connections[conn->handle]; + virDomainPtr dom = NULL; + int i, id = -1; + for (i = 0 ; i < MAX_DOMAINS ; i++) { + if (con->domains[i].active && + strcmp(name, con->domains[i].name) == 0) { + id = i; + break; + } + } + if (id >= 0) { + dom = virGetDomain(conn, con->domains[id].name, con->domains[id].uuid); + if (dom == NULL) { + testError(conn, NULL, VIR_ERR_NO_MEMORY, "Allocating domain"); + return(NULL); + } + dom->handle = id; + } + return dom; +} + +int testSimpleListDomains (virConnectPtr conn, + int *ids, + int maxids) +{ + testSimpleCon *con = &node.connections[conn->handle]; + int n, i; + + for (i = 0, n = 0 ; i < MAX_DOMAINS && n < maxids ; i++) { + if (con->domains[i].active) { + ids[n++] = i; + } + } + return n; +} + +int testSimpleDestroyDomain (virDomainPtr domain) +{ + testSimpleCon *con = &node.connections[domain->conn->handle]; + con->domains[domain->handle].active = 0; + return 0; +} + +int testSimpleResumeDomain (virDomainPtr domain) +{ + testSimpleCon *con = &node.connections[domain->conn->handle]; + con->domains[domain->handle].info.state = VIR_DOMAIN_RUNNING; + return 0; +} + +int testSimplePauseDomain (virDomainPtr domain) +{ + testSimpleCon *con = &node.connections[domain->conn->handle]; + con->domains[domain->handle].info.state = VIR_DOMAIN_PAUSED; + return 0; +} + +int testSimpleGetDomainInfo (virDomainPtr domain, + virDomainInfoPtr info) +{ + testSimpleCon *con = &node.connections[domain->conn->handle]; + struct timeval tv; + + if (gettimeofday(&tv, NULL) < 0) { + testError(NULL, NULL, VIR_ERR_INTERNAL_ERROR, "cannot get timeofday"); + return -1; + } + + con->domains[domain->handle].info.cpuTime = ((tv.tv_sec * 1000ll *1000ll) + tv.tv_usec) / 2; + memcpy(info, &con->domains[domain->handle].info, sizeof(virDomainInfo)); + return 0; +} + +int testSimpleSetMaxMemory (virDomainPtr domain, + unsigned long memory) +{ + testSimpleCon *con = &node.connections[domain->conn->handle]; + con->domains[domain->handle].info.maxMem = memory; + return 0; +} diff -r 5ec05a73e1fb -r 102f74a55c62 src/test_simple.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/test_simple.h Fri May 26 12:25:52 2006 -0400 @@ -0,0 +1,52 @@ +/* + * test.h: A "mock" hypervisor for use by application unit tests + * + * Copyright (C) 2006 Red Hat, Inc. + * + * See COPYING.LIB for the License of this software + * + * Daniel Berrange <berrange@xxxxxxxxxx> + */ + +#ifndef __VIR_TEST_SIMPLE_INTERNAL_H__ +#define __VIR_TEST_SIMPLE_INTERNAL_H__ + +#ifdef __cplusplus +extern "C" { +#endif + +#include <virterror.h> + + void testSimpleRegister(void); + int testSimpleOpen(virConnectPtr conn, + const char *name, + int flags); + int testSimpleClose (virConnectPtr conn); + int testSimpleGetVersion(virConnectPtr conn, + unsigned long *hvVer); + int testSimpleNodeGetInfo(virConnectPtr conn, + virNodeInfoPtr info); + int testSimpleNumOfDomains(virConnectPtr conn); + int testSimpleListDomains(virConnectPtr conn, + int *ids, + int maxids); + virDomainPtr testSimpleLookupDomainByID(virConnectPtr conn, + int id); + virDomainPtr testSimpleLookupDomainByUUID(virConnectPtr conn, + const unsigned char *uuid); + virDomainPtr testSimpleLookupDomainByName(virConnectPtr conn, + const char *name); + int testSimpleDestroyDomain(virDomainPtr domain); + int testSimpleResumeDomain(virDomainPtr domain); + int testSimplePauseDomain(virDomainPtr domain); + int testSimpleGetDomainInfo(virDomainPtr domain, + virDomainInfoPtr info); + int testSimpleGetDomainID(virDomainPtr domain); + const char*testSimpleGetDomainName(virDomainPtr domain); + int testSimpleSetMaxMemory(virDomainPtr domain, + unsigned long memory); + +#ifdef __cplusplus +} +#endif +#endif /* __VIR_TEST_INTERNAL_H__ */