Prior to this patch, every test:/// URI has its own event manager,
which means that registering for an event can only ever receive
events from the connection where it issued the API that triggered
the event. But the whole idea of events is to be able to learn
about something where an API call did NOT trigger the action.
In order to actually test asynchronous events, I wanted to be able
to tie multiple test connections to the same state. Use of a file
in a test URI is still per-connection state, but now parallel
connections to test:///default (from the same binary, of course)
now share common state and can affect one another.
Here's the test program I used to expose the difference (maybe not
the most polished, but does the trick):
#include <stdbool.h>
#include <stdlib.h>
#include <errno.h>
#include <stdio.h>
#include <unistd.h>
#include <signal.h>
#include <libvirt/libvirt.h>
#include <libvirt/virterror.h>
static int counter;
static int counter2;
static virConnectPtr conn1;
static virConnectPtr conn2;
static virDomainPtr dom1;
static virDomainPtr dom2;
static int
common(virConnectPtr conn, virDomainPtr dom, int event, int detail,
void *opaque, int caller)
{
printf("%d: in callback %d, from domain %s (%d), event/detail %d/%d\n",
counter2++, caller, virDomainGetName(dom),
virDomainGetID(dom), event, detail);
return 0;
}
static int
callback1(virConnectPtr conn, virDomainPtr dom, int event, int detail,
void *opaque)
{
return common(conn, dom, event, detail, opaque, 1);
}
static int
callback2(virConnectPtr conn, virDomainPtr dom, int event, int detail,
void *opaque)
{
return common(conn, dom, event, detail, opaque, 2);
}
static bool quit;
static void
handler(int sig)
{
quit = true;
}
static void
timer(int id, void *opaque)
{
printf("%d: timer %d firing\n", counter++, id);
unsigned long l;
switch (counter) {
case 1:
if (virDomainSuspend(dom2) < 0)
exit(10);
printf("suspended\n");
break;
case 2:
if (virDomainResume(dom2) < 0)
exit(11);
printf("resumed\n");
break;
case 4:
quit = true;
/* fallthrough */
default:
virConnectGetLibVersion(conn1, &l);
break;
}
}
int main(int argc, char **argv)
{
signal(SIGINT, handler);
conn1 = virConnectOpen("test:///default");
conn2 = virConnectOpen("test:///default");
if (!conn1 || !conn2)
return 1;
dom1 = virDomainLookupByName(conn1, "test");
dom2 = virDomainLookupByName(conn2, "test");
if (!dom1 || !dom2)
return 2;
if (virEventRegisterDefaultImpl() < 0)
return 3;
int id0 = virEventAddTimeout(1000, timer, NULL, NULL);
if (id0 < 0)
return 4;
int id1 = virConnectDomainEventRegisterAny(conn1, dom1,
VIR_DOMAIN_EVENT_ID_LIFECYCLE,
VIR_DOMAIN_EVENT_CALLBACK(callback1),
NULL, NULL);
if (id1 < 0)
return 5;
int id2 = -1;
if (argc > 1) {
id2 = virConnectDomainEventRegisterAny(conn2, dom2,
VIR_DOMAIN_EVENT_ID_LIFECYCLE,
VIR_DOMAIN_EVENT_CALLBACK(callback2),
NULL, NULL);
if (id2 < 0)
return 6;
if (argc > 2) {
if (virConnectDomainEventDeregisterAny(conn2, id2) < 0)
return 7;
}
}
while (!quit)
if (virEventRunDefaultImpl() < 0)
return 20;
if (virConnectDomainEventDeregisterAny(conn1, id1) < 0)
return 21;
if (id2 >= 0 && virConnectDomainEventDeregisterAny(conn2, id2) < 0)
return 22;
if (virEventRemoveTimeout(id0) < 0)
return 23;
if (virDomainFree(dom1) < 0 || virDomainFree(dom2) < 0 ||
virConnectClose(conn1) < 0 || virConnectClose(conn2) < 0)
return 24;
return 0;
}
Pre-patch results:
$ ./run ./bar
0: timer 1 firing
suspended
1: timer 1 firing
resumed
2: timer 1 firing
3: timer 1 firing
Post-patch results:
$ ./run ./bar
0: timer 1 firing
suspended
0: in callback 1, from domain test (1), event/detail 3/0
1: timer 1 firing
resumed
1: in callback 1, from domain test (1), event/detail 4/0
2: timer 1 firing
3: timer 1 firing
$ ./run ./bar 1
0: timer 1 firing
suspended
0: in callback 1, from domain test (1), event/detail 3/0
1: in callback 2, from domain test (1), event/detail 3/0
1: timer 1 firing
resumed
2: in callback 1, from domain test (1), event/detail 4/0
3: in callback 2, from domain test (1), event/detail 4/0
2: timer 1 firing
3: timer 1 firing
Valgrind didn't report any leaks.
* src/test/test_driver.c (testConnectOpen): Move per-connection
state initialization...
(testOpenFromFile): ...here.
(defaultConn, defaultConnections, defaultLock, testOnceInit): New
shared state.
(testOpenDefault): Only initialize on first connection.
(testConnectClose): Don't clobber state if still shared.
Signed-off-by: Eric Blake <eblake(a)redhat.com>
---
I'm actually playing with moving my test out of the commit message
and into tests/objecteventtest.c; if I get that working, I'll
post a v2.
src/test/test_driver.c | 88 ++++++++++++++++++++++++++++++++++++--------------
1 file changed, 64 insertions(+), 24 deletions(-)
diff --git a/src/test/test_driver.c b/src/test/test_driver.c
index a48404a..9696044 100644
--- a/src/test/test_driver.c
+++ b/src/test/test_driver.c
@@ -1,7 +1,7 @@
/*
* test.c: A "mock" hypervisor for use by application unit tests
*
- * Copyright (C) 2006-2013 Red Hat, Inc.
+ * Copyright (C) 2006-2014 Red Hat, Inc.
* Copyright (C) 2006 Daniel P. Berrange
*
* This library is free software; you can redistribute it and/or
@@ -105,6 +105,10 @@ struct _testConn {
typedef struct _testConn testConn;
typedef struct _testConn *testConnPtr;
+static testConn defaultConn;
+static int defaultConnections;
+static virMutex defaultLock;
+
#define TEST_MODEL "i686"
#define TEST_MODEL_WORDSIZE 32
#define TEST_EMULATOR "/usr/bin/test-hv"
@@ -125,6 +129,14 @@ static int testConnectClose(virConnectPtr conn);
static void testObjectEventQueue(testConnPtr driver,
virObjectEventPtr event);
+static int
+testOnceInit(void)
+{
+ return virMutexInit(&defaultLock);
+}
+
+VIR_ONCE_GLOBAL_INIT(test)
+
static void testDriverLock(testConnPtr driver)
{
@@ -665,9 +677,15 @@ cleanup:
return ret;
}
-static int testOpenDefault(virConnectPtr conn) {
+
+/* Simultaneous test:///default connections should share the same
+ * common state (among other things, this allows testing event
+ * detection in one connection for an action caused in another). */
+static int
+testOpenDefault(virConnectPtr conn)
+{
int u;
- testConnPtr privconn;
+ testConnPtr privconn = &defaultConn;
virDomainDefPtr domdef = NULL;
virDomainObjPtr domobj = NULL;
virNetworkDefPtr netdef = NULL;
@@ -679,18 +697,26 @@ static int testOpenDefault(virConnectPtr conn) {
virNodeDeviceDefPtr nodedef = NULL;
virNodeDeviceObjPtr nodeobj = NULL;
- if (VIR_ALLOC(privconn) < 0)
- return VIR_DRV_OPEN_ERROR;
+ virMutexLock(&defaultLock);
+ if (defaultConnections++) {
+ conn->privateData = &defaultConn;
+ virMutexUnlock(&defaultLock);
+ return VIR_DRV_OPEN_SUCCESS;
+ }
+
if (virMutexInit(&privconn->lock) < 0) {
virReportError(VIR_ERR_INTERNAL_ERROR,
"%s", _("cannot initialize mutex"));
- VIR_FREE(privconn);
+ defaultConnections--;
+ virMutexUnlock(&defaultLock);
return VIR_DRV_OPEN_ERROR;
}
- testDriverLock(privconn);
conn->privateData = privconn;
+ if (!(privconn->domainEventState = virObjectEventStateNew()))
+ goto error;
+
if (!(privconn->domains = virDomainObjListNew()))
goto error;
@@ -791,7 +817,7 @@ static int testOpenDefault(virConnectPtr conn) {
}
virNodeDeviceObjUnlock(nodeobj);
- testDriverUnlock(privconn);
+ virMutexUnlock(&defaultLock);
return VIR_DRV_OPEN_SUCCESS;
@@ -802,10 +828,12 @@ error:
virStoragePoolObjListFree(&privconn->pools);
virNodeDeviceObjListFree(&privconn->devs);
virObjectUnref(privconn->caps);
- testDriverUnlock(privconn);
+ virObjectEventStateFree(privconn->domainEventState);
+ virMutexDestroy(&privconn->lock);
conn->privateData = NULL;
- VIR_FREE(privconn);
virDomainDefFree(domdef);
+ defaultConnections--;
+ virMutexUnlock(&defaultLock);
return VIR_DRV_OPEN_ERROR;
}
@@ -1327,6 +1355,9 @@ error:
return ret;
}
+
+/* No shared state between simultaneous test connections initialized
+ * from a file. */
static int
testOpenFromFile(virConnectPtr conn, const char *file)
{
@@ -1355,6 +1386,9 @@ testOpenFromFile(virConnectPtr conn, const char *file)
if (!(privconn->xmlopt = testBuildXMLConfig()))
goto error;
+ if (!(privconn->domainEventState = virObjectEventStateNew()))
+ goto error;
+
if (!(doc = virXMLParseFileCtxt(file, &ctxt))) {
goto error;
}
@@ -1398,6 +1432,7 @@ testOpenFromFile(virConnectPtr conn, const char *file)
virInterfaceObjListFree(&privconn->ifaces);
virStoragePoolObjListFree(&privconn->pools);
VIR_FREE(privconn->path);
+ virObjectEventStateFree(privconn->domainEventState);
testDriverUnlock(privconn);
VIR_FREE(privconn);
conn->privateData = NULL;
@@ -1410,10 +1445,12 @@ static virDrvOpenStatus testConnectOpen(virConnectPtr conn,
unsigned int flags)
{
int ret;
- testConnPtr privconn;
virCheckFlags(VIR_CONNECT_RO, VIR_DRV_OPEN_ERROR);
+ if (testInitialize() < 0)
+ return VIR_DRV_OPEN_ERROR;
+
if (!conn->uri)
return VIR_DRV_OPEN_DECLINED;
@@ -1442,24 +1479,24 @@ static virDrvOpenStatus testConnectOpen(virConnectPtr conn,
if (ret != VIR_DRV_OPEN_SUCCESS)
return ret;
- privconn = conn->privateData;
- testDriverLock(privconn);
-
- privconn->domainEventState = virObjectEventStateNew();
- if (!privconn->domainEventState) {
- testDriverUnlock(privconn);
- testConnectClose(conn);
- return VIR_DRV_OPEN_ERROR;
- }
-
- testDriverUnlock(privconn);
-
return VIR_DRV_OPEN_SUCCESS;
}
static int testConnectClose(virConnectPtr conn)
{
testConnPtr privconn = conn->privateData;
+
+ if (testInitialize() < 0)
+ return -1;
+
+ if (privconn == &defaultConn) {
+ virMutexLock(&defaultLock);
+ if (--defaultConnections) {
+ virMutexUnlock(&defaultLock);
+ return 0;
+ }
+ }
+
testDriverLock(privconn);
virObjectUnref(privconn->caps);
virObjectUnref(privconn->xmlopt);
@@ -1474,7 +1511,10 @@ static int testConnectClose(virConnectPtr conn)
testDriverUnlock(privconn);
virMutexDestroy(&privconn->lock);
- VIR_FREE(privconn);
+ if (privconn == &defaultConn)
+ virMutexUnlock(&defaultLock);
+ else
+ VIR_FREE(privconn);
conn->privateData = NULL;
return 0;
}
--
1.8.4.2