On Thu, Mar 04, 2010 at 03:25:28PM +0000, Daniel P. Berrange wrote:
In the current domain APIs, we currently have support for getting
notified
of domain lifecycle transition events.
THis is done using two methods
int virConnectDomainEventRegister(virConnectPtr conn,
virConnectDomainEventCallback cb,
void *opaque,
virFreeCallback freecb);
int virConnectDomainEventDeregister(virConnectPtr conn,
virConnectDomainEventCallback cb);
This allows an app to register a callback that looks like this
typedef int (*virConnectDomainEventCallback)(virConnectPtr conn,
virDomainPtr dom,
int event,
int detail,
void *opaque);
Where 'event' is the lifecycle transition (suspended, stopped, started, etc)
and 'detail' is the cause of he transition (pause, migration, shutdown, etc)
I have outstanding feature requests to add a lot more event notifications
to the libvirt API. In particular
- IO Errors. Parameter 'alias' the name of the block device with the error
- Reboot. No parameters
You can argue whether this should be part of the lifecycle events. On
the one hand it is not guest visible, because the guest does a internal
machine reset without the host ever seeing a change. This is different
from other cases where QEMU itself is stopping/starting.
- Watchdog. Parameter 'action', saying what is going to happen to the
guest due to this watchdog firing (ignored, shutdown, paused, etc)
- VNC client. In fact three events, connect, authenticated and disconnect.
Parameters, TCP address & port number of client, and also of the server.
Optionally a SASL username and TLS certificate name of the authenticated
user
- Guest user. Logon/logoff events.
This requires co-operation from a guest agent, so it may not actually
be of scope of libvirt.
- Disk 'high watermark'. Emitted when a QCow volume grows beyond a certain
physical allocation. THis allows an app to enlarge the underlying storage
holding the qcow volume before an 'out of space' occurs. Parameter is
the disk alias name.
So we can see there are events with a wide variety of parameters and we need
to figure out how to represent this in the API.
Option 1
--------
Follow the existing lifecycle event model. For each new event, add a
virConnectXXXXXEventRegister & virConnectEventDeregister method, and
typedef a new callback for them. eg
typedef int (*virConnectDomainBlockIOEventCallback)(virConnectPtr conn,
virDomainPtr dom,
const char *diskname,
const char *alias,
void *opaque);
int virConnectDomainBlockIOEventRegister(virConnectPtr conn,
virConnectDomainBlockIOEventCallback cb,
void *opaque,
virFreeCallback freecb);
int virConnectDomainBlockIOEventDeregister(virConnectPtr conn,
virConnectDomainEventCallback cb);
So we'll have 2 extra APIs for every event + a new typedef. We'll also need
to add new APIs in src/conf/domain_event.h to cope with dispatch
I think it's a bit of an exageration, it's 2 extra API for every kind
of usages, where kind can be:
- Domain
- I/O
- Watchdog
- Guest Usage
Still that's not ideal. The pro's is that the domain event stuff
is "normal". That doesn't solve the callback structure problem as new
callback will be added to existing usage as time grows. We should
probably solve that generically instead.
Option 2
--------
GLib/GObject take a very loosely typed approach to registering/unregistering
events. The have a single pair of methods that work for any event & a generic
callback signature, requiring application casts.
typedef int (*virConnectEventCallback)(void *opaque);
int virConnectEventRegister(virConnectPtr conn,
const char *eventname,
virConnectEventCallback cb,
void *opaque,
virFreeCallback freecb);
int virCOnnectEventUnregister(virConnectPtr conn,
int eventID);
In this model, the register method returns a unique integer ID for the
callback which can be used to unregister it. Application's using this
will still need a strongly typed callback for receiving the event, but
when calling virConnectEventRegister(), the would do an explicit 'bad'
cast to 'virConnectEventCallback'
That fine to me except that for example if you want to bind to a
specific object (a domain, an interface) to catch his mutation, then
you need to pass this too, ending up with
int virConnectEventRegister(virConnectPtr conn,
void * object,
const char *eventname,
virConnectEventCallback cb,
void *opaque,
virFreeCallback freecb);
that means two casts. I like the unregister based on the ID token
though, that part can be completely generic.
That still doesn't solve the opaque callback object allocation, I'm
afraid, unless libvirt itself doesn't touch anything, but then that
mean that libvirt can only pass an event, but not provide any contextual
information (any of the extra stuff we use for Domain callbacks).
Option 3
--------
A hybrid of both approaches. Have a new 'register' method for each type of
event that takes a strongly typed callback, but have a generic 'unregister'
method that just uses the 'int eventID'
int virConnectDomainBlockIOEventRegister(virConnectPtr conn,
virConnectDomainBlockIOEventCallback cb,
void *opaque,
virFreeCallback freecb);
int virCOnnectEventUnregister(virConnectPtr conn,
int eventID);
Doesn't solve the real problem.
Option 4
--------
Have one pair of register/unregister events, but instead of passing diffeerent
parameters to each callback, have a generic callback that takes a single
parameter. This parameter would be declared as a union. So depending on
the type of event being received, you'd access different parts of the union
typedef union {
virConnectDomainBlockIOEvent blockio;
virConnectDomainWatchdogEvent watchdog;
...other events...
} virConnectEvent;
Either we could include a dummy member in the union with padding to 1024
bytes in size for future expansion, or we could simply declare that apps
must never allocate this data type themselves, thus allowing us to enlarge
it at will.
yeah, I know that glib has been doing that padding to ensure forward
compatibility over time. I find that a bit fishy. At least that solves
the allocation problem for data passed by libvirt as part of the
callback.
typedef int (*virConnectEventCallback)(int eventType,
virConnectEvent, void *opaque);
int virConnectEventRegister(virConnectPtr conn,
const char *eventname,
virConnectEventCallback cb,
void *opaque,
virFreeCallback freecb);
int virConnectEventUnregister(virConnectPtr conn,
int eventID);
I'm undecided yet between 1/ 2/ and 4/ there are pros and cons to all
of them in terms of API.
There is one final question unrelated to these 4 options. For the
lifecycle
events we always registered against the 'virConnectPtr' since that is
needed to capture 'domain created' events where there's no virDomainPtr
to register a callback against yet.
Do we want to always register all events aganist the virConnectPtr, and
then pass a 'virDomainPtr' as a parameter to the callbacks as needed. Or
yes I don't see how we could avoid adding an extra target object
should we allow registering events against the virDomainPtr
directly.
The latter might make it simpler to map libvirt into GLib/GObjects event
system in the future.
Then IMHO in that case we're stuck with 1/ , if we have per target
objects type of registrations, or as I suggested 2/ with an extra
void *object
Still unclear to me what's the best way. My main problem is how to
reconcile generic APIs entry point and the fact we have diverging data
in registration and provided at the callback level. I think we need
to have static per registration fixed data coming from the user and
managed by the user, plus something available from libvirt to give more
information about the callback, and that is hard to manage between
callback objects and over evolution of the library.
Daniel
--
Daniel Veillard | libxml Gnome XML XSLT toolkit
http://xmlsoft.org/
daniel(a)veillard.com | Rpmfind RPM search engine
http://rpmfind.net/
http://veillard.com/ | virtualization library
http://libvirt.org/