[Libvir] A 'mock' driver enabling application unit testing

Daniel P. Berrange berrange at redhat.com
Fri May 26 18:35:37 UTC 2006


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  -=| 
-------------- next part --------------
# HG changeset patch
# User "Daniel P. Berrange <berrange at redhat.com>"
# 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 at redhat.com>
+
+	* 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 at redhat.com>
 
 	* 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 at redhat.com>
+ */
+
+#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 at redhat.com>
+ */
+
+#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__ */


More information about the libvir-list mailing list