[libvirt] [PATCH v3 0/7] Provide an standard asyncio event loop impl

This patch series is a followup to Wojtek's v2 posting: https://www.redhat.com/archives/libvir-list/2017-March/msg00838.html It contains the fixes I pointed out in v1 / v2, along with a bunch of updates to the event-test.py example, so we can use it to demonstrate the asyncio impl. eg i'm using: $ python3 ./examples/event-test.py --loop=asyncio --timeout=30 qemu:///session Daniel P. Berrange (5): event-test: free opaque data when removing callbacks event-test: add timeout to exit event loop event-test: unregister callbacks & close conn on exit event-test: rename example event loop impl event-test: add ability to run the asyncio event loop Wojtek Porczyk (2): Allow for ff callbacks to be called by custom event implementations Add asyncio event loop implementation MANIFEST.in | 1 + examples/event-test.py | 194 +++++++++++++++--------- libvirt-override.c | 68 +++++---- libvirt-override.py | 23 +++ libvirt-python.spec.in | 2 + libvirtaio.py | 399 +++++++++++++++++++++++++++++++++++++++++++++++++ sanitytest.py | 3 +- setup.py | 12 ++ 8 files changed, 603 insertions(+), 99 deletions(-) create mode 100644 libvirtaio.py -- 2.9.3

From: Wojtek Porczyk <woju@invisiblethingslab.com> The documentation says:
If the opaque user data requires free'ing when the handle is unregistered, then a 2nd callback can be supplied for this purpose. This callback needs to be invoked from a clean stack. If 'ff' callbacks are invoked directly from the virEventRemoveHandleFunc they will likely deadlock in libvirt.
And they did deadlock. In removeTimeout too. Now we supply a custom function to pick it from the opaque blob and fire. Signed-off-by: Wojtek Porczyk <woju@invisiblethingslab.com> Signed-off-by: Daniel P. Berrange <berrange@redhat.com> --- libvirt-override.c | 68 ++++++++++++++++++++++++++++++----------------------- libvirt-override.py | 23 ++++++++++++++++++ sanitytest.py | 3 ++- 3 files changed, 63 insertions(+), 31 deletions(-) diff --git a/libvirt-override.c b/libvirt-override.c index 93c7ef0..a762941 100644 --- a/libvirt-override.c +++ b/libvirt-override.c @@ -5223,6 +5223,9 @@ libvirt_virEventAddHandleFunc(int fd, VIR_PY_TUPLE_SET_GOTO(pyobj_args, 3, cb_args, cleanup); + /* If changing contents of the opaque object, please also change + * virEventInvokeFreeCallback() in libvirt-override.py + */ VIR_PY_TUPLE_SET_GOTO(cb_args, 0, libvirt_virEventHandleCallbackWrap(cb), cleanup); VIR_PY_TUPLE_SET_GOTO(cb_args, 1, libvirt_virVoidPtrWrap(opaque), cleanup); VIR_PY_TUPLE_SET_GOTO(cb_args, 2, libvirt_virFreeCallbackWrap(ff), cleanup); @@ -5279,10 +5282,7 @@ libvirt_virEventRemoveHandleFunc(int watch) { PyObject *result = NULL; PyObject *pyobj_args; - PyObject *opaque; - PyObject *ff; int retval = -1; - virFreeCallback cff; LIBVIRT_ENSURE_THREAD_STATE; @@ -5292,20 +5292,11 @@ libvirt_virEventRemoveHandleFunc(int watch) VIR_PY_TUPLE_SET_GOTO(pyobj_args, 0, libvirt_intWrap(watch), cleanup); result = PyEval_CallObject(removeHandleObj, pyobj_args); - if (!result) { + if (result) { + retval = 0; + } else { PyErr_Print(); PyErr_Clear(); - } else if (!PyTuple_Check(result) || PyTuple_Size(result) != 3) { - DEBUG("%s: %s must return opaque obj registered with %s" - "to avoid leaking libvirt memory\n", - __FUNCTION__, NAME(removeHandle), NAME(addHandle)); - } else { - opaque = PyTuple_GetItem(result, 1); - ff = PyTuple_GetItem(result, 2); - cff = PyvirFreeCallback_Get(ff); - if (cff) - (*cff)(PyvirVoidPtr_Get(opaque)); - retval = 0; } cleanup: @@ -5350,6 +5341,9 @@ libvirt_virEventAddTimeoutFunc(int timeout, VIR_PY_TUPLE_SET_GOTO(pyobj_args, 2, cb_args, cleanup); + /* If changing contents of the opaque object, please also change + * virEventInvokeFreeCallback() in libvirt-override.py + */ VIR_PY_TUPLE_SET_GOTO(cb_args, 0, libvirt_virEventTimeoutCallbackWrap(cb), cleanup); VIR_PY_TUPLE_SET_GOTO(cb_args, 1, libvirt_virVoidPtrWrap(opaque), cleanup); VIR_PY_TUPLE_SET_GOTO(cb_args, 2, libvirt_virFreeCallbackWrap(ff), cleanup); @@ -5403,10 +5397,7 @@ libvirt_virEventRemoveTimeoutFunc(int timer) { PyObject *result = NULL; PyObject *pyobj_args; - PyObject *opaque; - PyObject *ff; int retval = -1; - virFreeCallback cff; LIBVIRT_ENSURE_THREAD_STATE; @@ -5416,20 +5407,11 @@ libvirt_virEventRemoveTimeoutFunc(int timer) VIR_PY_TUPLE_SET_GOTO(pyobj_args, 0, libvirt_intWrap(timer), cleanup); result = PyEval_CallObject(removeTimeoutObj, pyobj_args); - if (!result) { + if (result) { + retval = 0; + } else { PyErr_Print(); PyErr_Clear(); - } else if (!PyTuple_Check(result) || PyTuple_Size(result) != 3) { - DEBUG("%s: %s must return opaque obj registered with %s" - "to avoid leaking libvirt memory\n", - __FUNCTION__, NAME(removeTimeout), NAME(addTimeout)); - } else { - opaque = PyTuple_GetItem(result, 1); - ff = PyTuple_GetItem(result, 2); - cff = PyvirFreeCallback_Get(ff); - if (cff) - (*cff)(PyvirVoidPtr_Get(opaque)); - retval = 0; } cleanup: @@ -5558,6 +5540,31 @@ libvirt_virEventInvokeTimeoutCallback(PyObject *self ATTRIBUTE_UNUSED, return VIR_PY_INT_SUCCESS; } +static PyObject * +libvirt_virEventInvokeFreeCallback(PyObject *self ATTRIBUTE_UNUSED, + PyObject *args) +{ + PyObject *py_f; + PyObject *py_opaque; + virFreeCallback cb; + void *opaque; + + if (!PyArg_ParseTuple(args, (char *) "OO:virEventInvokeFreeCallback", + &py_f, &py_opaque)) + return NULL; + + cb = (virFreeCallback) PyvirEventHandleCallback_Get(py_f); + opaque = (void *) PyvirVoidPtr_Get(py_opaque); + + if (cb) { + LIBVIRT_BEGIN_ALLOW_THREADS; + cb(opaque); + LIBVIRT_END_ALLOW_THREADS; + } + + return VIR_PY_INT_SUCCESS; +} + static void libvirt_virEventHandleCallback(int watch, int fd, @@ -9572,6 +9579,7 @@ static PyMethodDef libvirtMethods[] = { {(char *) "virEventAddTimeout", libvirt_virEventAddTimeout, METH_VARARGS, NULL}, {(char *) "virEventInvokeHandleCallback", libvirt_virEventInvokeHandleCallback, METH_VARARGS, NULL}, {(char *) "virEventInvokeTimeoutCallback", libvirt_virEventInvokeTimeoutCallback, METH_VARARGS, NULL}, + {(char *) "virEventInvokeFreeCallback", libvirt_virEventInvokeFreeCallback, METH_VARARGS, NULL}, {(char *) "virNodeListDevices", libvirt_virNodeListDevices, METH_VARARGS, NULL}, #if LIBVIR_CHECK_VERSION(0, 10, 2) {(char *) "virConnectListAllNodeDevices", libvirt_virConnectListAllNodeDevices, METH_VARARGS, NULL}, diff --git a/libvirt-override.py b/libvirt-override.py index 63f8ecb..b0b24ef 100644 --- a/libvirt-override.py +++ b/libvirt-override.py @@ -211,3 +211,26 @@ def virEventAddTimeout(timeout, cb, opaque): ret = libvirtmod.virEventAddTimeout(timeout, cbData) if ret == -1: raise libvirtError ('virEventAddTimeout() failed') return ret + + +# +# a caller for the ff callbacks for custom event loop implementations +# + +def virEventInvokeFreeCallback(opaque): + """ + Execute callback which frees the opaque buffer + + @opaque: the opaque object passed to addHandle or addTimeout + + WARNING: This function should not be called from any call by libvirt's + core. It will most probably cause deadlock in C-level libvirt code. + Instead it should be scheduled and called from implementation's stack. + + See https://libvirt.org/html/libvirt-libvirt-event.html#virEventAddHandleFunc + for more information. + + This function is not dependent on any event loop implementation. + """ + + libvirtmod.virEventInvokeFreeCallback(opaque[2], opaque[1]) diff --git a/sanitytest.py b/sanitytest.py index a140ba2..7183baa 100644 --- a/sanitytest.py +++ b/sanitytest.py @@ -349,7 +349,8 @@ for klass in gotfunctions: continue for func in sorted(gotfunctions[klass]): # These are pure python methods with no C APi - if func in ["connect", "getConnect", "domain", "getDomain"]: + if func in ["connect", "getConnect", "domain", "getDomain", + "virEventInvokeFreeCallback"]: continue key = "%s.%s" % (klass, func) -- 2.9.3

From: Wojtek Porczyk <woju@invisiblethingslab.com> This is usable only on python >= 3.4 (or 3.3 with out-of-tree asyncio), however it should be harmless for anyone with older python versions. In simplest case, to have the callbacks queued on the default loop: >>> import libvirtaio >>> libvirtaio.virEventRegisterAsyncIOImpl() The function is not present on non-compatible platforms. Signed-off-by: Wojtek Porczyk <woju@invisiblethingslab.com> --- MANIFEST.in | 1 + libvirt-python.spec.in | 2 + libvirtaio.py | 399 +++++++++++++++++++++++++++++++++++++++++++++++++ setup.py | 12 ++ 4 files changed, 414 insertions(+) create mode 100644 libvirtaio.py diff --git a/MANIFEST.in b/MANIFEST.in index 0d66f9c..b6788f4 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -31,6 +31,7 @@ include libvirt-qemu-override-api.xml include libvirt-qemu-override.c include libvirt-utils.h include libvirt-utils.c +include libvirtaio.py include MANIFEST include README include sanitytest.py diff --git a/libvirt-python.spec.in b/libvirt-python.spec.in index 3021ebd..5ad0292 100644 --- a/libvirt-python.spec.in +++ b/libvirt-python.spec.in @@ -86,11 +86,13 @@ rm -f %{buildroot}%{_libdir}/python*/site-packages/*egg-info %defattr(-,root,root) %doc ChangeLog AUTHORS NEWS README COPYING COPYING.LESSER examples/ %{_libdir}/python3*/site-packages/libvirt.py* +%{_libdir}/python3*/site-packages/libvirtaio.py* %{_libdir}/python3*/site-packages/libvirt_qemu.py* %{_libdir}/python3*/site-packages/libvirt_lxc.py* %{_libdir}/python3*/site-packages/__pycache__/libvirt.cpython-*.py* %{_libdir}/python3*/site-packages/__pycache__/libvirt_qemu.cpython-*.py* %{_libdir}/python3*/site-packages/__pycache__/libvirt_lxc.cpython-*.py* +%{_libdir}/python3*/site-packages/__pycache__/libvirtaio.cpython-*.py* %{_libdir}/python3*/site-packages/libvirtmod* %endif diff --git a/libvirtaio.py b/libvirtaio.py new file mode 100644 index 0000000..7c8c396 --- /dev/null +++ b/libvirtaio.py @@ -0,0 +1,399 @@ +# +# libvirtaio -- asyncio adapter for libvirt +# Copyright (C) 2017 Wojtek Porczyk <woju@invisiblethingslab.com> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, see +# <http://www.gnu.org/licenses/>. +# + +'''Libvirt event loop implementation using asyncio + +Register the implementation of default loop: + + >>> import libvirtaio + >>> libvirtaio.virEventRegisterAsyncIOImpl() + +.. seealso:: + https://libvirt.org/html/libvirt-libvirt-event.html +''' + +__author__ = 'Wojtek Porczyk <woju@invisiblethingslab.com>' +__license__ = 'LGPL-2.1+' +__all__ = ['virEventAsyncIOImpl', 'virEventRegisterAsyncIOImpl'] + +import asyncio +import itertools +import logging +import warnings + +import libvirt + +try: + from asyncio import ensure_future +except ImportError: + from asyncio import async as ensure_future + + +class Callback(object): + '''Base class for holding callback + + :param virEventAsyncIOImpl impl: the implementation in which we run + :param cb: the callback itself + :param opaque: the opaque tuple passed by libvirt + ''' + # pylint: disable=too-few-public-methods + + _iden_counter = itertools.count() + + def __init__(self, impl, cb, opaque, *args, **kwargs): + super().__init__(*args, **kwargs) + self.iden = next(self._iden_counter) + self.impl = impl + self.cb = cb + self.opaque = opaque + + assert self.iden not in self.impl.callbacks, \ + 'found {} callback: {!r}'.format( + self.iden, self.impl.callbacks[self.iden]) + self.impl.callbacks[self.iden] = self + + def __repr__(self): + return '<{} iden={}>'.format(self.__class__.__name__, self.iden) + + def close(self): + '''Schedule *ff* callback''' + self.impl.log.debug('callback %d close(), scheduling ff', self.iden) + self.impl.schedule_ff_callback(self.opaque) + +# +# file descriptors +# + +class Descriptor(object): + '''Manager of one file descriptor + + :param virEventAsyncIOImpl impl: the implementation in which we run + :param int fd: the file descriptor + ''' + def __init__(self, impl, fd): + self.impl = impl + self.fd = fd + self.callbacks = {} + + def _handle(self, event): + '''Dispatch the event to the descriptors + + :param int event: The event (from libvirt's constants) being dispatched + ''' + for callback in self.callbacks.values(): + if callback.event is not None and callback.event & event: + callback.cb(callback.iden, self.fd, event, callback.opaque) + + def update(self): + '''Register or unregister callbacks at event loop + + This should be called after change of any ``.event`` in callbacks. + ''' + # It seems like loop.add_{reader,writer} can be run multiple times + # and will still register the callback only once. Likewise, + # remove_{reader,writer} may be run even if the reader/writer + # is not registered (and will just return False). + + # For the edge case of empty callbacks, any() returns False. + if any(callback.event & ~( + libvirt.VIR_EVENT_HANDLE_READABLE | + libvirt.VIR_EVENT_HANDLE_WRITABLE) + for callback in self.callbacks.values()): + warnings.warn( + 'The only event supported are VIR_EVENT_HANDLE_READABLE ' + 'and VIR_EVENT_HANDLE_WRITABLE', + UserWarning) + + if any(callback.event & libvirt.VIR_EVENT_HANDLE_READABLE + for callback in self.callbacks.values()): + self.impl.loop.add_reader( + self.fd, self._handle, libvirt.VIR_EVENT_HANDLE_READABLE) + else: + self.impl.loop.remove_reader(self.fd) + + if any(callback.event & libvirt.VIR_EVENT_HANDLE_WRITABLE + for callback in self.callbacks.values()): + self.impl.loop.add_writer( + self.fd, self._handle, libvirt.VIR_EVENT_HANDLE_WRITABLE) + else: + self.impl.loop.remove_writer(self.fd) + + def add_handle(self, callback): + '''Add a callback to the descriptor + + :param FDCallback callback: the callback to add + :rtype: None + + After adding the callback, it is immediately watched. + ''' + self.callbacks[callback.iden] = callback + self.update() + + def remove_handle(self, iden): + '''Remove a callback from the descriptor + + :param int iden: the identifier of the callback + :returns: the callback + :rtype: FDCallback + + After removing the callback, the descriptor may be unwatched, if there + are no more handles for it. + ''' + callback = self.callbacks.pop(iden) + self.update() + return callback + + def close(self): + '''''' + self.callbacks.clear() + self.update() + +class DescriptorDict(dict): + '''Descriptors collection + + This is used internally by virEventAsyncIOImpl to hold descriptors. + ''' + def __init__(self, impl): + super().__init__() + self.impl = impl + + def __missing__(self, fd): + descriptor = Descriptor(self.impl, fd) + self[fd] = descriptor + return descriptor + +class FDCallback(Callback): + '''Callback for file descriptor (watcher) + + :param Descriptor descriptor: the descriptor manager + :param int event: bitset of events on which to fire the callback + ''' + # pylint: disable=too-few-public-methods + + def __init__(self, *args, descriptor, event, **kwargs): + super().__init__(*args, **kwargs) + self.descriptor = descriptor + self.event = event + + def __repr__(self): + return '<{} iden={} fd={} event={}>'.format( + self.__class__.__name__, self.iden, self.descriptor.fd, self.event) + + def update(self, event): + '''Update the callback and fix descriptor's watchers''' + self.event = event + self.descriptor.update() + +# +# timeouts +# + +class TimeoutCallback(Callback): + '''Callback for timer''' + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.timeout = -1 + self._task = None + + def __repr__(self): + return '<{} iden={} timeout={}>'.format( + self.__class__.__name__, self.iden, self.timeout) + + @asyncio.coroutine + def _timer(self): + '''An actual timer running on the event loop. + + This is a coroutine. + ''' + while True: + try: + if self.timeout > 0: + timeout = self.timeout * 1e-3 + self.impl.log.debug('sleeping %r', timeout) + yield from asyncio.sleep(timeout) + else: + # scheduling timeout for next loop iteration + yield + + except asyncio.CancelledError: + self.impl.log.debug('timer %d cancelled', self.iden) + break + + self.cb(self.iden, self.opaque) + self.impl.log.debug('timer %r callback ended', self.iden) + + def update(self, timeout): + '''Start or the timer, possibly updating timeout''' + self.timeout = timeout + + if self.timeout >= 0 and self._task is None: + self.impl.log.debug('timer %r start', self.iden) + self._task = ensure_future(self._timer(), + loop=self.impl.loop) + + elif self.timeout < 0 and self._task is not None: + self.impl.log.debug('timer %r stop', self.iden) + self._task.cancel() # pylint: disable=no-member + self._task = None + + def close(self): + '''Stop the timer and call ff callback''' + super(TimeoutCallback, self).close() + self.update(timeout=-1) + +# +# main implementation +# + +class virEventAsyncIOImpl(object): + '''Libvirt event adapter to asyncio. + + :param loop: asyncio's event loop + + If *loop* is not specified, the current (or default) event loop is used. + ''' + + def __init__(self, loop=None): + self.loop = loop or asyncio.get_event_loop() + self.callbacks = {} + self.descriptors = DescriptorDict(self) + self.log = logging.getLogger(self.__class__.__name__) + + def register(self): + '''Register this instance as event loop implementation''' + # pylint: disable=bad-whitespace + self.log.debug('register()') + libvirt.virEventRegisterImpl( + self._add_handle, self._update_handle, self._remove_handle, + self._add_timeout, self._update_timeout, self._remove_timeout) + return self + + def schedule_ff_callback(self, opaque): + '''Schedule a ff callback from one of the handles or timers''' + self.loop.call_soon(libvirt.virEventInvokeFreeCallback, opaque) + + def is_idle(self): + '''Returns False if there are leftovers from a connection + + Those may happen if there are sematical problems while closing + a connection. For example, not deregistered events before .close(). + ''' + return not self.callbacks + + def _add_handle(self, fd, event, cb, opaque): + '''Register a callback for monitoring file handle events + + :param int fd: file descriptor to listen on + :param int event: bitset of events on which to fire the callback + :param cb: the callback to be called when an event occurrs + :param opaque: user data to pass to the callback + :rtype: int + :returns: handle watch number to be used for updating and unregistering for events + + .. seealso:: + https://libvirt.org/html/libvirt-libvirt-event.html#virEventAddHandleFuncFun... + ''' + self.log.debug('add_handle(fd=%d, event=%d, cb=%r, opaque=%r)', + fd, event, cb, opaque) + callback = FDCallback(self, cb, opaque, + descriptor=self.descriptors[fd], event=event) + self.callbacks[callback.iden] = callback + self.descriptors[fd].add_handle(callback) + return callback.iden + + def _update_handle(self, watch, event): + '''Change event set for a monitored file handle + + :param int watch: file descriptor watch to modify + :param int event: new events to listen on + + .. seealso:: + https://libvirt.org/html/libvirt-libvirt-event.html#virEventUpdateHandleFunc + ''' + self.log.debug('update_handle(watch=%d, event=%d)', watch, event) + return self.callbacks[watch].update(event=event) + + def _remove_handle(self, watch): + '''Unregister a callback from a file handle. + + :param int watch: file descriptor watch to stop listening on + :returns: None (see source for explanation) + + .. seealso:: + https://libvirt.org/html/libvirt-libvirt-event.html#virEventRemoveHandleFunc + ''' + self.log.debug('remove_handle(watch=%d)', watch) + callback = self.callbacks.pop(watch) + fd = callback.descriptor.fd + assert callback is self.descriptors[fd].remove_handle(watch) + if len(self.descriptors[fd].callbacks) == 0: + del self.descriptors[fd] + callback.close() + + def _add_timeout(self, timeout, cb, opaque): + '''Register a callback for a timer event + + :param int timeout: the timeout to monitor + :param cb: the callback to call when timeout has expired + :param opaque: user data to pass to the callback + :rtype: int + :returns: a timer value + + .. seealso:: + https://libvirt.org/html/libvirt-libvirt-event.html#virEventAddTimeoutFunc + ''' + self.log.debug('add_timeout(timeout=%d, cb=%r, opaque=%r)', + timeout, cb, opaque) + callback = TimeoutCallback(self, cb, opaque) + self.callbacks[callback.iden] = callback + callback.update(timeout=timeout) + return callback.iden + + def _update_timeout(self, timer, timeout): + '''Change frequency for a timer + + :param int timer: the timer to modify + :param int timeout: the new timeout value in ms + + .. seealso:: + https://libvirt.org/html/libvirt-libvirt-event.html#virEventUpdateTimeoutFun... + ''' + self.log.debug('update_timeout(timer=%d, timeout=%d)', timer, timeout) + return self.callbacks[timer].update(timeout=timeout) + + def _remove_timeout(self, timer): + '''Unregister a callback for a timer + + :param int timer: the timer to remove + :returns: None (see source for explanation) + + .. seealso:: + https://libvirt.org/html/libvirt-libvirt-event.html#virEventRemoveTimeoutFun... + ''' + self.log.debug('remove_timeout(timer=%d)', timer) + callback = self.callbacks.pop(timer) + callback.close() + +def virEventRegisterAsyncIOImpl(loop=None): + '''Arrange for libvirt's callbacks to be dispatched via asyncio event loop + + The implementation object is returned, but in normal usage it can safely be + discarded. + ''' + return virEventAsyncIOImpl(loop=loop).register() diff --git a/setup.py b/setup.py index 71998e2..c95170c 100755 --- a/setup.py +++ b/setup.py @@ -14,6 +14,7 @@ import sys import os import os.path import re +import shutil import time MIN_LIBVIRT = "0.9.11" @@ -50,6 +51,12 @@ def have_libvirt_lxc(): except DistutilsExecError: return False +def have_libvirtaio(): + # This depends on asyncio, which in turn depends on "yield from" syntax. + # The asyncio module itself is in standard library since 3.4, but there is + # an out-of-tree version compatible with 3.3. + return sys.version_info >= (3, 3) + def get_pkgconfig_data(args, mod, required=True): """Run pkg-config to and return content associated with it""" f = os.popen("%s %s %s" % (get_pkgcfg(), " ".join(args), mod)) @@ -124,6 +131,9 @@ def get_module_lists(): c_modules.append(modulelxc) py_modules.append("libvirt_lxc") + if have_libvirtaio(): + py_modules.append("libvirtaio") + return c_modules, py_modules @@ -141,6 +151,8 @@ class my_build(build): self.spawn([sys.executable, "generator.py", "libvirt-qemu", apis[1]]) if have_libvirt_lxc(): self.spawn([sys.executable, "generator.py", "libvirt-lxc", apis[2]]) + if have_libvirtaio(): + shutil.copy('libvirtaio.py', 'build') build.run(self) -- 2.9.3

The pure python event loop impl has to call libvirt.virEventInvokeFreeCallback to free the event opaque data from a clean stack context Signed-off-by: Daniel P. Berrange <berrange@redhat.com> --- examples/event-test.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/examples/event-test.py b/examples/event-test.py index a1105a3..851c09b 100755 --- a/examples/event-test.py +++ b/examples/event-test.py @@ -107,6 +107,7 @@ class virEventLoopPure: self.nextTimerID = 1 self.handles = [] self.timers = [] + self.cleanup = [] self.quit = False # The event loop can be used from multiple threads at once. @@ -178,6 +179,11 @@ class virEventLoopPure: def run_once(self): sleep = -1 self.runningPoll = True + + for opaque in self.cleanup: + libvirt.virEventInvokeFreeCallback(opaque) + self.cleanup = [] + try: next = self.next_timeout() debug("Next timeout due at %d" % next) @@ -300,8 +306,9 @@ class virEventLoopPure: handles = [] for h in self.handles: if h.get_id() == handleID: - self.poll.unregister(h.get_fd()) debug("Remove handle %d fd %d" % (handleID, h.get_fd())) + self.poll.unregister(h.get_fd()) + self.cleanup.append(h.opaque) else: handles.append(h) self.handles = handles @@ -313,7 +320,9 @@ class virEventLoopPure: for h in self.timers: if h.get_id() != timerID: timers.append(h) + else: debug("Remove timer %d" % timerID) + self.cleanup.append(h.opaque) self.timers = timers self.interrupt() -- 2.9.3

Signed-off-by: Daniel P. Berrange <berrange@redhat.com> --- examples/event-test.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/examples/event-test.py b/examples/event-test.py index 851c09b..751a140 100755 --- a/examples/event-test.py +++ b/examples/event-test.py @@ -651,15 +651,17 @@ def usage(): print(" --help, -h Print(this help message") print(" --debug, -d Print(debug output") print(" --loop, -l Toggle event-loop-implementation") + print(" --timeout=SECS Quit after SECS seconds running") def main(): try: - opts, args = getopt.getopt(sys.argv[1:], "hdl", ["help", "debug", "loop"]) + opts, args = getopt.getopt(sys.argv[1:], "hdl", ["help", "debug", "loop", "timeout="]) except getopt.GetoptError as err: # print help information and exit: print(str(err)) # will print something like "option -a not recognized" usage() sys.exit(2) + timeout = None for o, a in opts: if o in ("-h", "--help"): usage() @@ -670,6 +672,8 @@ def main(): if o in ("-l", "--loop"): global use_pure_python_event_loop use_pure_python_event_loop ^= True + if o in ("--timeout"): + timeout = int(a) if len(args) >= 1: uri = args[0] @@ -741,7 +745,9 @@ def main(): # of demo we'll just go to sleep. The other option is to # run the event loop in your main thread if your app is # totally event based. - while run: + count = 0 + while run and (timeout is None or count < timeout): + count = count + 1 time.sleep(1) -- 2.9.3

In order to test cleanup code paths we must unregister all callbacks and close the connection on shutdown. Since cleanup happens in the background, we do a short sleep to allow the main loop to run its cleanup too. Signed-off-by: Daniel P. Berrange <berrange@redhat.com> --- examples/event-test.py | 95 +++++++++++++++++++++++++++++++------------------- 1 file changed, 59 insertions(+), 36 deletions(-) diff --git a/examples/event-test.py b/examples/event-test.py index 751a140..ac9fbe1 100755 --- a/examples/event-test.py +++ b/examples/event-test.py @@ -702,42 +702,47 @@ def main(): #Add 2 lifecycle callbacks to prove this works with more than just one vc.domainEventRegister(myDomainEventCallback1,None) - vc.domainEventRegisterAny(None, libvirt.VIR_DOMAIN_EVENT_ID_LIFECYCLE, myDomainEventCallback2, None) - vc.domainEventRegisterAny(None, libvirt.VIR_DOMAIN_EVENT_ID_REBOOT, myDomainEventRebootCallback, None) - vc.domainEventRegisterAny(None, libvirt.VIR_DOMAIN_EVENT_ID_RTC_CHANGE, myDomainEventRTCChangeCallback, None) - vc.domainEventRegisterAny(None, libvirt.VIR_DOMAIN_EVENT_ID_WATCHDOG, myDomainEventWatchdogCallback, None) - vc.domainEventRegisterAny(None, libvirt.VIR_DOMAIN_EVENT_ID_IO_ERROR, myDomainEventIOErrorCallback, None) - vc.domainEventRegisterAny(None, libvirt.VIR_DOMAIN_EVENT_ID_GRAPHICS, myDomainEventGraphicsCallback, None) - vc.domainEventRegisterAny(None, libvirt.VIR_DOMAIN_EVENT_ID_IO_ERROR_REASON, myDomainEventIOErrorReasonCallback, None) - vc.domainEventRegisterAny(None, libvirt.VIR_DOMAIN_EVENT_ID_CONTROL_ERROR, myDomainEventControlErrorCallback, None) - vc.domainEventRegisterAny(None, libvirt.VIR_DOMAIN_EVENT_ID_BLOCK_JOB, myDomainEventBlockJobCallback, None) - vc.domainEventRegisterAny(None, libvirt.VIR_DOMAIN_EVENT_ID_DISK_CHANGE, myDomainEventDiskChangeCallback, None) - vc.domainEventRegisterAny(None, libvirt.VIR_DOMAIN_EVENT_ID_TRAY_CHANGE, myDomainEventTrayChangeCallback, None) - vc.domainEventRegisterAny(None, libvirt.VIR_DOMAIN_EVENT_ID_PMWAKEUP, myDomainEventPMWakeupCallback, None) - vc.domainEventRegisterAny(None, libvirt.VIR_DOMAIN_EVENT_ID_PMSUSPEND, myDomainEventPMSuspendCallback, None) - vc.domainEventRegisterAny(None, libvirt.VIR_DOMAIN_EVENT_ID_BALLOON_CHANGE, myDomainEventBalloonChangeCallback, None) - vc.domainEventRegisterAny(None, libvirt.VIR_DOMAIN_EVENT_ID_PMSUSPEND_DISK, myDomainEventPMSuspendDiskCallback, None) - vc.domainEventRegisterAny(None, libvirt.VIR_DOMAIN_EVENT_ID_DEVICE_REMOVED, myDomainEventDeviceRemovedCallback, None) - vc.domainEventRegisterAny(None, libvirt.VIR_DOMAIN_EVENT_ID_BLOCK_JOB_2, myDomainEventBlockJob2Callback, None) - vc.domainEventRegisterAny(None, libvirt.VIR_DOMAIN_EVENT_ID_TUNABLE, myDomainEventTunableCallback, None) - vc.domainEventRegisterAny(None, libvirt.VIR_DOMAIN_EVENT_ID_AGENT_LIFECYCLE, myDomainEventAgentLifecycleCallback, None) - vc.domainEventRegisterAny(None, libvirt.VIR_DOMAIN_EVENT_ID_DEVICE_ADDED, myDomainEventDeviceAddedCallback, None) - vc.domainEventRegisterAny(None, libvirt.VIR_DOMAIN_EVENT_ID_MIGRATION_ITERATION, myDomainEventMigrationIteration, None) - vc.domainEventRegisterAny(None, libvirt.VIR_DOMAIN_EVENT_ID_JOB_COMPLETED, myDomainEventJobCompletedCallback, None) - vc.domainEventRegisterAny(None, libvirt.VIR_DOMAIN_EVENT_ID_DEVICE_REMOVAL_FAILED, myDomainEventDeviceRemovalFailedCallback, None) - vc.domainEventRegisterAny(None, libvirt.VIR_DOMAIN_EVENT_ID_METADATA_CHANGE, myDomainEventMetadataChangeCallback, None) - vc.domainEventRegisterAny(None, libvirt.VIR_DOMAIN_EVENT_ID_BLOCK_THRESHOLD, myDomainEventBlockThresholdCallback, None) - - vc.networkEventRegisterAny(None, libvirt.VIR_NETWORK_EVENT_ID_LIFECYCLE, myNetworkEventLifecycleCallback, None) - - vc.storagePoolEventRegisterAny(None, libvirt.VIR_STORAGE_POOL_EVENT_ID_LIFECYCLE, myStoragePoolEventLifecycleCallback, None) - vc.storagePoolEventRegisterAny(None, libvirt.VIR_STORAGE_POOL_EVENT_ID_REFRESH, myStoragePoolEventRefreshCallback, None) - - vc.nodeDeviceEventRegisterAny(None, libvirt.VIR_NODE_DEVICE_EVENT_ID_LIFECYCLE, myNodeDeviceEventLifecycleCallback, None) - vc.nodeDeviceEventRegisterAny(None, libvirt.VIR_NODE_DEVICE_EVENT_ID_UPDATE, myNodeDeviceEventUpdateCallback, None) - - vc.secretEventRegisterAny(None, libvirt.VIR_SECRET_EVENT_ID_LIFECYCLE, mySecretEventLifecycleCallback, None) - vc.secretEventRegisterAny(None, libvirt.VIR_SECRET_EVENT_ID_VALUE_CHANGED, mySecretEventValueChanged, None) + domcallbacks = [] + domcallbacks.append(vc.domainEventRegisterAny(None, libvirt.VIR_DOMAIN_EVENT_ID_LIFECYCLE, myDomainEventCallback2, None)) + domcallbacks.append(vc.domainEventRegisterAny(None, libvirt.VIR_DOMAIN_EVENT_ID_REBOOT, myDomainEventRebootCallback, None)) + domcallbacks.append(vc.domainEventRegisterAny(None, libvirt.VIR_DOMAIN_EVENT_ID_RTC_CHANGE, myDomainEventRTCChangeCallback, None)) + domcallbacks.append(vc.domainEventRegisterAny(None, libvirt.VIR_DOMAIN_EVENT_ID_WATCHDOG, myDomainEventWatchdogCallback, None)) + domcallbacks.append(vc.domainEventRegisterAny(None, libvirt.VIR_DOMAIN_EVENT_ID_IO_ERROR, myDomainEventIOErrorCallback, None)) + domcallbacks.append(vc.domainEventRegisterAny(None, libvirt.VIR_DOMAIN_EVENT_ID_GRAPHICS, myDomainEventGraphicsCallback, None)) + domcallbacks.append(vc.domainEventRegisterAny(None, libvirt.VIR_DOMAIN_EVENT_ID_IO_ERROR_REASON, myDomainEventIOErrorReasonCallback, None)) + domcallbacks.append(vc.domainEventRegisterAny(None, libvirt.VIR_DOMAIN_EVENT_ID_CONTROL_ERROR, myDomainEventControlErrorCallback, None)) + domcallbacks.append(vc.domainEventRegisterAny(None, libvirt.VIR_DOMAIN_EVENT_ID_BLOCK_JOB, myDomainEventBlockJobCallback, None)) + domcallbacks.append(vc.domainEventRegisterAny(None, libvirt.VIR_DOMAIN_EVENT_ID_DISK_CHANGE, myDomainEventDiskChangeCallback, None)) + domcallbacks.append(vc.domainEventRegisterAny(None, libvirt.VIR_DOMAIN_EVENT_ID_TRAY_CHANGE, myDomainEventTrayChangeCallback, None)) + domcallbacks.append(vc.domainEventRegisterAny(None, libvirt.VIR_DOMAIN_EVENT_ID_PMWAKEUP, myDomainEventPMWakeupCallback, None)) + domcallbacks.append(vc.domainEventRegisterAny(None, libvirt.VIR_DOMAIN_EVENT_ID_PMSUSPEND, myDomainEventPMSuspendCallback, None)) + domcallbacks.append(vc.domainEventRegisterAny(None, libvirt.VIR_DOMAIN_EVENT_ID_BALLOON_CHANGE, myDomainEventBalloonChangeCallback, None)) + domcallbacks.append(vc.domainEventRegisterAny(None, libvirt.VIR_DOMAIN_EVENT_ID_PMSUSPEND_DISK, myDomainEventPMSuspendDiskCallback, None)) + domcallbacks.append(vc.domainEventRegisterAny(None, libvirt.VIR_DOMAIN_EVENT_ID_DEVICE_REMOVED, myDomainEventDeviceRemovedCallback, None)) + domcallbacks.append(vc.domainEventRegisterAny(None, libvirt.VIR_DOMAIN_EVENT_ID_BLOCK_JOB_2, myDomainEventBlockJob2Callback, None)) + domcallbacks.append(vc.domainEventRegisterAny(None, libvirt.VIR_DOMAIN_EVENT_ID_TUNABLE, myDomainEventTunableCallback, None)) + domcallbacks.append(vc.domainEventRegisterAny(None, libvirt.VIR_DOMAIN_EVENT_ID_AGENT_LIFECYCLE, myDomainEventAgentLifecycleCallback, None)) + domcallbacks.append(vc.domainEventRegisterAny(None, libvirt.VIR_DOMAIN_EVENT_ID_DEVICE_ADDED, myDomainEventDeviceAddedCallback, None)) + domcallbacks.append(vc.domainEventRegisterAny(None, libvirt.VIR_DOMAIN_EVENT_ID_MIGRATION_ITERATION, myDomainEventMigrationIteration, None)) + domcallbacks.append(vc.domainEventRegisterAny(None, libvirt.VIR_DOMAIN_EVENT_ID_JOB_COMPLETED, myDomainEventJobCompletedCallback, None)) + domcallbacks.append(vc.domainEventRegisterAny(None, libvirt.VIR_DOMAIN_EVENT_ID_DEVICE_REMOVAL_FAILED, myDomainEventDeviceRemovalFailedCallback, None)) + domcallbacks.append(vc.domainEventRegisterAny(None, libvirt.VIR_DOMAIN_EVENT_ID_METADATA_CHANGE, myDomainEventMetadataChangeCallback, None)) + domcallbacks.append(vc.domainEventRegisterAny(None, libvirt.VIR_DOMAIN_EVENT_ID_BLOCK_THRESHOLD, myDomainEventBlockThresholdCallback, None)) + + netcallbacks = [] + netcallbacks.append(vc.networkEventRegisterAny(None, libvirt.VIR_NETWORK_EVENT_ID_LIFECYCLE, myNetworkEventLifecycleCallback, None)) + + poolcallbacks = [] + poolcallbacks.append(vc.storagePoolEventRegisterAny(None, libvirt.VIR_STORAGE_POOL_EVENT_ID_LIFECYCLE, myStoragePoolEventLifecycleCallback, None)) + poolcallbacks.append(vc.storagePoolEventRegisterAny(None, libvirt.VIR_STORAGE_POOL_EVENT_ID_REFRESH, myStoragePoolEventRefreshCallback, None)) + + devcallbacks = [] + devcallbacks.append(vc.nodeDeviceEventRegisterAny(None, libvirt.VIR_NODE_DEVICE_EVENT_ID_LIFECYCLE, myNodeDeviceEventLifecycleCallback, None)) + devcallbacks.append(vc.nodeDeviceEventRegisterAny(None, libvirt.VIR_NODE_DEVICE_EVENT_ID_UPDATE, myNodeDeviceEventUpdateCallback, None)) + + seccallbacks = [] + seccallbacks.append(vc.secretEventRegisterAny(None, libvirt.VIR_SECRET_EVENT_ID_LIFECYCLE, mySecretEventLifecycleCallback, None)) + seccallbacks.append(vc.secretEventRegisterAny(None, libvirt.VIR_SECRET_EVENT_ID_VALUE_CHANGED, mySecretEventValueChanged, None)) vc.setKeepAlive(5, 3) @@ -750,6 +755,24 @@ def main(): count = count + 1 time.sleep(1) + vc.domainEventDeregister(myDomainEventCallback1) + + for id in seccallbacks: + vc.secretEventDeregisterAny(id) + for id in devcallbacks: + vc.nodeDeviceEventDeregisterAny(id) + for id in poolcallbacks: + vc.storagePoolEventDeregisterAny(id) + for id in netcallbacks: + vc.networkEventDeregisterAny(id) + for id in domcallbacks: + vc.domainEventDeregisterAny(id) + + vc.unregisterCloseCallback() + vc.close() + + # Allow delayed event loop cleanup to run, just for sake of testing + time.sleep(2) if __name__ == "__main__": main() -- 2.9.3

Use the name 'Poll' instead of 'Pure' for the event loop demo, since there's now a second pure python loop impl available. Signed-off-by: Daniel P. Berrange <berrange@redhat.com> --- examples/event-test.py | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/examples/event-test.py b/examples/event-test.py index ac9fbe1..e67984f 100755 --- a/examples/event-test.py +++ b/examples/event-test.py @@ -39,10 +39,10 @@ def debug(msg): # # It is a pure python implementation based around the poll() API # -class virEventLoopPure: +class virEventLoopPoll: # This class contains the data we need to track for a # single file handle - class virEventLoopPureHandle: + class virEventLoopPollHandle: def __init__(self, handle, fd, events, cb, opaque): self.handle = handle self.fd = fd @@ -70,7 +70,7 @@ class virEventLoopPure: # This class contains the data we need to track for a # single periodic timer - class virEventLoopPureTimer: + class virEventLoopPollTimer: def __init__(self, timer, interval, cb, opaque): self.timer = timer self.interval = interval @@ -142,14 +142,14 @@ class virEventLoopPure: return next - # Lookup a virEventLoopPureHandle object based on file descriptor + # Lookup a virEventLoopPollHandle object based on file descriptor def get_handle_by_fd(self, fd): for h in self.handles: if h.get_fd() == fd: return h return None - # Lookup a virEventLoopPureHandle object based on its event loop ID + # Lookup a virEventLoopPollHandle object based on its event loop ID def get_handle_by_id(self, handleID): for h in self.handles: if h.get_id() == handleID: @@ -253,7 +253,7 @@ class virEventLoopPure: handleID = self.nextHandleID + 1 self.nextHandleID = self.nextHandleID + 1 - h = self.virEventLoopPureHandle(handleID, fd, events, cb, opaque) + h = self.virEventLoopPollHandle(handleID, fd, events, cb, opaque) self.handles.append(h) self.poll.register(fd, self.events_to_poll(events)) @@ -272,7 +272,7 @@ class virEventLoopPure: timerID = self.nextTimerID + 1 self.nextTimerID = self.nextTimerID + 1 - h = self.virEventLoopPureTimer(timerID, interval, cb, opaque) + h = self.virEventLoopPollTimer(timerID, interval, cb, opaque) self.timers.append(h) self.interrupt() @@ -361,7 +361,7 @@ class virEventLoopPure: # This single global instance of the event loop wil be used for # monitoring libvirt events -eventLoop = virEventLoopPure() +eventLoop = virEventLoopPoll() # This keeps track of what thread is running the event loop, # (if it is run in a background thread) @@ -371,7 +371,7 @@ eventLoopThread = None # These next set of 6 methods are the glue between the official # libvirt events API, and our particular impl of the event loop # -# There is no reason why the 'virEventLoopPure' has to be used. +# There is no reason why the 'virEventLoopPoll' has to be used. # An application could easily may these 6 glue methods hook into # another event loop such as GLib's, or something like the python # Twisted event framework. @@ -402,7 +402,7 @@ def virEventRemoveTimerImpl(timerID): # This tells libvirt what event loop implementation it # should use -def virEventLoopPureRegister(): +def virEventLoopPollRegister(): libvirt.virEventRegisterImpl(virEventAddHandleImpl, virEventUpdateHandleImpl, virEventRemoveHandleImpl, @@ -411,7 +411,7 @@ def virEventLoopPureRegister(): virEventRemoveTimerImpl) # Directly run the event loop in the current thread -def virEventLoopPureRun(): +def virEventLoopPollRun(): global eventLoop eventLoop.run_loop() @@ -420,10 +420,10 @@ def virEventLoopNativeRun(): libvirt.virEventRunDefaultImpl() # Spawn a background thread to run the event loop -def virEventLoopPureStart(): +def virEventLoopPollStart(): global eventLoopThread - virEventLoopPureRegister() - eventLoopThread = threading.Thread(target=virEventLoopPureRun, name="libvirtEventLoop") + virEventLoopPollRegister() + eventLoopThread = threading.Thread(target=virEventLoopPollRun, name="libvirtEventLoop") eventLoopThread.setDaemon(True) eventLoopThread.start() @@ -684,7 +684,7 @@ def main(): # Run a background thread with the event loop if use_pure_python_event_loop: - virEventLoopPureStart() + virEventLoopPollStart() else: virEventLoopNativeStart() -- 2.9.3

The event test program '--loop' arg is modified to take the name of an event loop impl to run. eg 'event-test.py --loop asyncio' Signed-off-by: Daniel P. Berrange <berrange@redhat.com> --- examples/event-test.py | 50 +++++++++++++++++++++++++++++++++++--------------- 1 file changed, 35 insertions(+), 15 deletions(-) diff --git a/examples/event-test.py b/examples/event-test.py index e67984f..3bca9e2 100755 --- a/examples/event-test.py +++ b/examples/event-test.py @@ -15,16 +15,19 @@ import errno import time import threading -# For the sake of demonstration, this example program includes -# an implementation of a pure python event loop. Most applications -# would be better off just using the default libvirt event loop -# APIs, instead of implementing this in python. The exception is -# where an application wants to integrate with an existing 3rd -# party event loop impl +# This example can use three different event loop impls. It defaults +# to a portable pure-python impl based on poll that is implemented +# in this file. # -# Change this to 'False' to make the demo use the native -# libvirt event loop impl -use_pure_python_event_loop = True +# When Python >= 3.4, it can optionally use an impl based on the +# new asyncio module. +# +# Finally, it can also use the libvirt native event loop impl +# +# This setting thus allows 'poll', 'native' or 'asyncio' as valid +# choices +# +event_impl = "poll" do_debug = False def debug(msg): @@ -415,6 +418,11 @@ def virEventLoopPollRun(): global eventLoop eventLoop.run_loop() +def virEventLoopAIORun(loop): + import asyncio + asyncio.set_event_loop(loop) + loop.run_forever() + def virEventLoopNativeRun(): while True: libvirt.virEventRunDefaultImpl() @@ -427,6 +435,16 @@ def virEventLoopPollStart(): eventLoopThread.setDaemon(True) eventLoopThread.start() +def virEventLoopAIOStart(): + global eventLoopThread + import libvirtaio + import asyncio + loop = asyncio.new_event_loop() + libvirtaio.virEventRegisterAsyncIOImpl(loop=loop) + eventLoopThread = threading.Thread(target=virEventLoopAIORun, args=(loop,), name="libvirtEventLoop") + eventLoopThread.setDaemon(True) + eventLoopThread.start() + def virEventLoopNativeStart(): global eventLoopThread libvirt.virEventRegisterDefaultImpl() @@ -650,12 +668,12 @@ def usage(): print(" uri will default to qemu:///system") print(" --help, -h Print(this help message") print(" --debug, -d Print(debug output") - print(" --loop, -l Toggle event-loop-implementation") + print(" --loop=TYPE, -l Choose event-loop-implementation (native, poll, asyncio)") print(" --timeout=SECS Quit after SECS seconds running") def main(): try: - opts, args = getopt.getopt(sys.argv[1:], "hdl", ["help", "debug", "loop", "timeout="]) + opts, args = getopt.getopt(sys.argv[1:], "hdl:", ["help", "debug", "loop=", "timeout="]) except getopt.GetoptError as err: # print help information and exit: print(str(err)) # will print something like "option -a not recognized" @@ -670,8 +688,8 @@ def main(): global do_debug do_debug = True if o in ("-l", "--loop"): - global use_pure_python_event_loop - use_pure_python_event_loop ^= True + global event_impl + event_impl = a if o in ("--timeout"): timeout = int(a) @@ -680,11 +698,13 @@ def main(): else: uri = "qemu:///system" - print("Using uri:" + uri) + print("Using uri '%s' and event loop '%s'" % (uri, event_impl)) # Run a background thread with the event loop - if use_pure_python_event_loop: + if event_impl == "poll": virEventLoopPollStart() + elif event_impl == "asyncio": + virEventLoopAIOStart() else: virEventLoopNativeStart() -- 2.9.3

On 04/04/2017 04:31 PM, Daniel P. Berrange wrote:
This patch series is a followup to Wojtek's v2 posting:
https://www.redhat.com/archives/libvir-list/2017-March/msg00838.html
It contains the fixes I pointed out in v1 / v2, along with a bunch of updates to the event-test.py example, so we can use it to demonstrate the asyncio impl. eg i'm using:
$ python3 ./examples/event-test.py --loop=asyncio --timeout=30 qemu:///session
Daniel P. Berrange (5): event-test: free opaque data when removing callbacks event-test: add timeout to exit event loop event-test: unregister callbacks & close conn on exit event-test: rename example event loop impl event-test: add ability to run the asyncio event loop
Wojtek Porczyk (2): Allow for ff callbacks to be called by custom event implementations Add asyncio event loop implementation
MANIFEST.in | 1 + examples/event-test.py | 194 +++++++++++++++--------- libvirt-override.c | 68 +++++---- libvirt-override.py | 23 +++ libvirt-python.spec.in | 2 + libvirtaio.py | 399 +++++++++++++++++++++++++++++++++++++++++++++++++ sanitytest.py | 3 +- setup.py | 12 ++ 8 files changed, 603 insertions(+), 99 deletions(-) create mode 100644 libvirtaio.py
The subject doesn't say so, but the diffstat does. This is against libvirt-python. ACK series. Michal

On Tue, Apr 04, 2017 at 03:31:27PM +0100, Daniel P. Berrange wrote:
$ python3 ./examples/event-test.py --loop=asyncio --timeout=30 qemu:///session
Thank you for this update. I tested it backported to 3.1.0 with xen:/// using event-test.py and also with our own code. Looks good to me. I encountered one small problem, which I believe is orthogonal to the patch series: On Tue, Apr 04, 2017 at 03:31:32PM +0100, Daniel P. Berrange wrote:
diff --git a/examples/event-test.py b/examples/event-test.py index 751a140..ac9fbe1 100755 --- a/examples/event-test.py +++ b/examples/event-test.py
+ netcallbacks = [] + netcallbacks.append(vc.networkEventRegisterAny(None, libvirt.VIR_NETWORK_EVENT_ID_LIFECYCLE, myNetworkEventLifecycleCallback, None))
With vc = libvirt.open('xen:///') and with libvirt{,-python}-3.1.0 this line causes an exception: libvirt.libvirtError: this function is not supported by the connection driver: virConnectNetworkEventRegisterAny Commenting it out solves the problem. -- pozdrawiam / best regards _.-._ Wojtek Porczyk .-^' '^-. Invisible Things Lab |'-.-^-.-'| | | | | I do not fear computers, | '-.-' | I fear lack of them. '-._ : ,-' -- Isaac Asimov `^-^-_>

On 04/06/2017 10:47 PM, Wojtek Porczyk wrote:
On Tue, Apr 04, 2017 at 03:31:27PM +0100, Daniel P. Berrange wrote:
$ python3 ./examples/event-test.py --loop=asyncio --timeout=30 qemu:///session
Thank you for this update. I tested it backported to 3.1.0 with xen:/// using event-test.py and also with our own code. Looks good to me.
I encountered one small problem, which I believe is orthogonal to the patch series:
On Tue, Apr 04, 2017 at 03:31:32PM +0100, Daniel P. Berrange wrote:
diff --git a/examples/event-test.py b/examples/event-test.py index 751a140..ac9fbe1 100755 --- a/examples/event-test.py +++ b/examples/event-test.py
+ netcallbacks = [] + netcallbacks.append(vc.networkEventRegisterAny(None, libvirt.VIR_NETWORK_EVENT_ID_LIFECYCLE, myNetworkEventLifecycleCallback, None))
With vc = libvirt.open('xen:///') and with libvirt{,-python}-3.1.0 this line causes an exception:
libvirt.libvirtError: this function is not supported by the connection driver: virConnectNetworkEventRegisterAny
That's because we don't have a network driver for XEN. Usually, for stateful drivers (those which require daemon to run, e.g. qemu, lxc, ..) they can use our own bridge driver for network (it creates a bridge to which domain NICs are plugged in). However, then we have bunch of stateless driver for which libvirt is just a wrapper over their APIs. For those we would need a separate network driver that does the API wrapping. And it looks like there's none for XEN. Not sure whether there's something to wrap though. I mean I've no idea what XEN networking capabilities are. But what we can do here is to surround networkEvenRegisterAny() call with try {} except () block and ignore ENOSUPP error. Michal

On Fri, Apr 07, 2017 at 09:31:45AM +0200, Michal Privoznik wrote:
On 04/06/2017 10:47 PM, Wojtek Porczyk wrote:
libvirt.libvirtError: this function is not supported by the connection driver: virConnectNetworkEventRegisterAny
That's because we don't have a network driver for XEN. Usually, for stateful drivers (those which require daemon to run, e.g. qemu, lxc, ..) they can use our own bridge driver for network (it creates a bridge to which domain NICs are plugged in). However, then we have bunch of stateless driver for which libvirt is just a wrapper over their APIs. For those we would need a separate network driver that does the API wrapping. And it looks like there's none for XEN. Not sure whether there's something to wrap though. I mean I've no idea what XEN networking capabilities are.
But what we can do here is to surround networkEvenRegisterAny() call with try {} except () block and ignore ENOSUPP error.
Thanks for the explanation. Are there any other drivers which don't support some events? If so, maybe all the event requests should be wrapped in that? -- pozdrawiam / best regards _.-._ Wojtek Porczyk .-^' '^-. Invisible Things Lab |'-.-^-.-'| | | | | I do not fear computers, | '-.-' | I fear lack of them. '-._ : ,-' -- Isaac Asimov `^-^-_>

On Fri, Apr 07, 2017 at 09:31:45AM +0200, Michal Privoznik wrote:
On 04/06/2017 10:47 PM, Wojtek Porczyk wrote:
On Tue, Apr 04, 2017 at 03:31:27PM +0100, Daniel P. Berrange wrote:
$ python3 ./examples/event-test.py --loop=asyncio --timeout=30 qemu:///session
Thank you for this update. I tested it backported to 3.1.0 with xen:/// using event-test.py and also with our own code. Looks good to me.
I encountered one small problem, which I believe is orthogonal to the patch series:
On Tue, Apr 04, 2017 at 03:31:32PM +0100, Daniel P. Berrange wrote:
diff --git a/examples/event-test.py b/examples/event-test.py index 751a140..ac9fbe1 100755 --- a/examples/event-test.py +++ b/examples/event-test.py
+ netcallbacks = [] + netcallbacks.append(vc.networkEventRegisterAny(None, libvirt.VIR_NETWORK_EVENT_ID_LIFECYCLE, myNetworkEventLifecycleCallback, None))
With vc = libvirt.open('xen:///') and with libvirt{,-python}-3.1.0 this line causes an exception:
libvirt.libvirtError: this function is not supported by the connection driver: virConnectNetworkEventRegisterAny
That's because we don't have a network driver for XEN. Usually, for stateful drivers (those which require daemon to run, e.g. qemu, lxc, ..) they can use our own bridge driver for network (it creates a bridge to which domain NICs are plugged in). However, then we have bunch of stateless driver for which libvirt is just a wrapper over their APIs. For those we would need a separate network driver that does the API wrapping. And it looks like there's none for XEN. Not sure whether there's something to wrap though. I mean I've no idea what XEN networking capabilities are.
That doesn't make sense - the shared network driver should be operational for Xen - it certainly worked in the past. Regards, Daniel -- |: http://berrange.com -o- http://www.flickr.com/photos/dberrange/ :| |: http://libvirt.org -o- http://virt-manager.org :| |: http://entangle-photo.org -o- http://search.cpan.org/~danberr/ :|

On Thu, Apr 06, 2017 at 10:47:48PM +0200, Wojtek Porczyk wrote:
On Tue, Apr 04, 2017 at 03:31:27PM +0100, Daniel P. Berrange wrote:
$ python3 ./examples/event-test.py --loop=asyncio --timeout=30 qemu:///session
Thank you for this update. I tested it backported to 3.1.0 with xen:/// using event-test.py and also with our own code. Looks good to me.
Great, thank you.
I encountered one small problem, which I believe is orthogonal to the patch series:
On Tue, Apr 04, 2017 at 03:31:32PM +0100, Daniel P. Berrange wrote:
diff --git a/examples/event-test.py b/examples/event-test.py index 751a140..ac9fbe1 100755 --- a/examples/event-test.py +++ b/examples/event-test.py
+ netcallbacks = [] + netcallbacks.append(vc.networkEventRegisterAny(None, libvirt.VIR_NETWORK_EVENT_ID_LIFECYCLE, myNetworkEventLifecycleCallback, None))
With vc = libvirt.open('xen:///') and with libvirt{,-python}-3.1.0 this line causes an exception:
libvirt.libvirtError: this function is not supported by the connection driver: virConnectNetworkEventRegisterAny
Commenting it out solves the problem.
Yes, I must say the event test is only validated with the KVM driver, but that said I thought the Xen driver would get our default nework driver activated automatically, so I'm surprised you see that. Regards, Daniel -- |: http://berrange.com -o- http://www.flickr.com/photos/dberrange/ :| |: http://libvirt.org -o- http://virt-manager.org :| |: http://entangle-photo.org -o- http://search.cpan.org/~danberr/ :|

On Fri, Apr 07, 2017 at 10:22:38AM +0100, Daniel P. Berrange wrote:
On Thu, Apr 06, 2017 at 10:47:48PM +0200, Wojtek Porczyk wrote:
On Tue, Apr 04, 2017 at 03:31:27PM +0100, Daniel P. Berrange wrote:
$ python3 ./examples/event-test.py --loop=asyncio --timeout=30 qemu:///session
Thank you for this update. I tested it backported to 3.1.0 with xen:/// using event-test.py and also with our own code. Looks good to me.
Great, thank you.
I encountered one small problem, which I believe is orthogonal to the patch series:
On Tue, Apr 04, 2017 at 03:31:32PM +0100, Daniel P. Berrange wrote:
diff --git a/examples/event-test.py b/examples/event-test.py index 751a140..ac9fbe1 100755 --- a/examples/event-test.py +++ b/examples/event-test.py
+ netcallbacks = [] + netcallbacks.append(vc.networkEventRegisterAny(None, libvirt.VIR_NETWORK_EVENT_ID_LIFECYCLE, myNetworkEventLifecycleCallback, None))
With vc = libvirt.open('xen:///') and with libvirt{,-python}-3.1.0 this line causes an exception:
libvirt.libvirtError: this function is not supported by the connection driver: virConnectNetworkEventRegisterAny
Commenting it out solves the problem.
Yes, I must say the event test is only validated with the KVM driver, but that said I thought the Xen driver would get our default nework driver activated automatically, so I'm surprised you see that.
I don't know, maybe this is caused by our setup. For example we have no NIC in dom0 (only loopback) and we also may have deliberately broken something around the network. Cc: marmarek -- pozdrawiam / best regards _.-._ Wojtek Porczyk .-^' '^-. Invisible Things Lab |'-.-^-.-'| | | | | I do not fear computers, | '-.-' | I fear lack of them. '-._ : ,-' -- Isaac Asimov `^-^-_>

On Fri, Apr 07, 2017 at 01:46:12PM +0200, Wojtek Porczyk wrote:
On Fri, Apr 07, 2017 at 10:22:38AM +0100, Daniel P. Berrange wrote:
On Thu, Apr 06, 2017 at 10:47:48PM +0200, Wojtek Porczyk wrote:
On Tue, Apr 04, 2017 at 03:31:27PM +0100, Daniel P. Berrange wrote:
$ python3 ./examples/event-test.py --loop=asyncio --timeout=30 qemu:///session
Thank you for this update. I tested it backported to 3.1.0 with xen:/// using event-test.py and also with our own code. Looks good to me.
Great, thank you.
I encountered one small problem, which I believe is orthogonal to the patch series:
On Tue, Apr 04, 2017 at 03:31:32PM +0100, Daniel P. Berrange wrote:
diff --git a/examples/event-test.py b/examples/event-test.py index 751a140..ac9fbe1 100755 --- a/examples/event-test.py +++ b/examples/event-test.py
+ netcallbacks = [] + netcallbacks.append(vc.networkEventRegisterAny(None, libvirt.VIR_NETWORK_EVENT_ID_LIFECYCLE, myNetworkEventLifecycleCallback, None))
With vc = libvirt.open('xen:///') and with libvirt{,-python}-3.1.0 this line causes an exception:
libvirt.libvirtError: this function is not supported by the connection driver: virConnectNetworkEventRegisterAny
Commenting it out solves the problem.
Yes, I must say the event test is only validated with the KVM driver, but that said I thought the Xen driver would get our default nework driver activated automatically, so I'm surprised you see that.
I don't know, maybe this is caused by our setup. For example we have no NIC in dom0 (only loopback) and we also may have deliberately broken something around the network.
Yes, our packages have ./configure (...) --without-network. -- Best Regards, Marek Marczykowski-Górecki Invisible Things Lab A: Because it messes up the order in which people normally read text. Q: Why is top-posting such a bad thing?

On Fri, Apr 07, 2017 at 02:53:52PM +0200, Marek Marczykowski-Górecki wrote:
On Fri, Apr 07, 2017 at 01:46:12PM +0200, Wojtek Porczyk wrote:
On Fri, Apr 07, 2017 at 10:22:38AM +0100, Daniel P. Berrange wrote:
On Thu, Apr 06, 2017 at 10:47:48PM +0200, Wojtek Porczyk wrote:
I encountered one small problem, which I believe is orthogonal to the patch series:
On Tue, Apr 04, 2017 at 03:31:32PM +0100, Daniel P. Berrange wrote:
diff --git a/examples/event-test.py b/examples/event-test.py index 751a140..ac9fbe1 100755 --- a/examples/event-test.py +++ b/examples/event-test.py
+ netcallbacks = [] + netcallbacks.append(vc.networkEventRegisterAny(None, libvirt.VIR_NETWORK_EVENT_ID_LIFECYCLE, myNetworkEventLifecycleCallback, None))
With vc = libvirt.open('xen:///') and with libvirt{,-python}-3.1.0 this line causes an exception:
libvirt.libvirtError: this function is not supported by the connection driver: virConnectNetworkEventRegisterAny
Commenting it out solves the problem.
Yes, I must say the event test is only validated with the KVM driver, but that said I thought the Xen driver would get our default nework driver activated automatically, so I'm surprised you see that.
I don't know, maybe this is caused by our setup. For example we have no NIC in dom0 (only loopback) and we also may have deliberately broken something around the network.
Yes, our packages have ./configure (...) --without-network.
So looks like our fault. Sorry for that. -- pozdrawiam / best regards _.-._ Wojtek Porczyk .-^' '^-. Invisible Things Lab |'-.-^-.-'| | | | | I do not fear computers, | '-.-' | I fear lack of them. '-._ : ,-' -- Isaac Asimov `^-^-_>

On Fri, Apr 07, 2017 at 06:35:54PM +0200, Wojtek Porczyk wrote:
On Fri, Apr 07, 2017 at 02:53:52PM +0200, Marek Marczykowski-Górecki wrote:
On Fri, Apr 07, 2017 at 01:46:12PM +0200, Wojtek Porczyk wrote:
On Fri, Apr 07, 2017 at 10:22:38AM +0100, Daniel P. Berrange wrote:
On Thu, Apr 06, 2017 at 10:47:48PM +0200, Wojtek Porczyk wrote:
I encountered one small problem, which I believe is orthogonal to the patch series:
On Tue, Apr 04, 2017 at 03:31:32PM +0100, Daniel P. Berrange wrote:
diff --git a/examples/event-test.py b/examples/event-test.py index 751a140..ac9fbe1 100755 --- a/examples/event-test.py +++ b/examples/event-test.py
+ netcallbacks = [] + netcallbacks.append(vc.networkEventRegisterAny(None, libvirt.VIR_NETWORK_EVENT_ID_LIFECYCLE, myNetworkEventLifecycleCallback, None))
With vc = libvirt.open('xen:///') and with libvirt{,-python}-3.1.0 this line causes an exception:
libvirt.libvirtError: this function is not supported by the connection driver: virConnectNetworkEventRegisterAny
Commenting it out solves the problem.
Yes, I must say the event test is only validated with the KVM driver, but that said I thought the Xen driver would get our default nework driver activated automatically, so I'm surprised you see that.
I don't know, maybe this is caused by our setup. For example we have no NIC in dom0 (only loopback) and we also may have deliberately broken something around the network.
Yes, our packages have ./configure (...) --without-network.
So looks like our fault. Sorry for that.
No problem, good to know everything's working as expected Regards, Daniel -- |: http://berrange.com -o- http://www.flickr.com/photos/dberrange/ :| |: http://libvirt.org -o- http://virt-manager.org :| |: http://entangle-photo.org -o- http://search.cpan.org/~danberr/ :|

On Thu, Apr 06, 2017 at 10:47:48PM +0200, Wojtek Porczyk wrote:
On Tue, Apr 04, 2017 at 03:31:27PM +0100, Daniel P. Berrange wrote:
$ python3 ./examples/event-test.py --loop=asyncio --timeout=30 qemu:///session
Thank you for this update. I tested it backported to 3.1.0 with xen:/// using event-test.py and also with our own code. Looks good to me.
FYI, this series is now pushed to git master. Thanks for your contribution to libvirt ! Regards, Daniel -- |: http://berrange.com -o- http://www.flickr.com/photos/dberrange/ :| |: http://libvirt.org -o- http://virt-manager.org :| |: http://entangle-photo.org -o- http://search.cpan.org/~danberr/ :|
participants (4)
-
Daniel P. Berrange
-
Marek Marczykowski-Górecki
-
Michal Privoznik
-
Wojtek Porczyk