[libvirt] [PATCH libvirt-python 0/2] event-test.py fixes

Hello, event-test.py is bad at handling newer livecycle events. Attached are two patches to improve that. Philipp Hahn (2): event-test.py: Sync list of domain lifecycle events event-test.py: Future proof lifecycle event handling examples/event-test.py | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) -- 2.11.0

Add new events to prevent crash:
raceback (most recent call last): File "/usr/lib/python2.7/dist-packages/libvirt.py", line 4601, in _dispatchDomainEventCallbacks cb(self, virDomain(self, _obj=dom), event, detail, opaque) File "libvirt-python/examples/event-test.py", line 505, in myDomainEventCallback1 domDetailToString(event, detail))) File "libvirt-python/examples/event-test.py", line 484, in domDetailToString return domEventStrings[event][detail] IndexError: tuple index out of range
Signed-off-by: Philipp Hahn <hahn@univention.de> --- examples/event-test.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/examples/event-test.py b/examples/event-test.py index 281e661..04310e1 100755 --- a/examples/event-test.py +++ b/examples/event-test.py @@ -471,11 +471,11 @@ def domEventToString(event): def domDetailToString(event, detail): domEventStrings = ( - ( "Added", "Updated" ), - ( "Removed", ), + ( "Added", "Updated", "Renamed", "Snapshot" ), + ( "Removed", "Renamed", ), ( "Booted", "Migrated", "Restored", "Snapshot", "Wakeup" ), - ( "Paused", "Migrated", "IOError", "Watchdog", "Restored", "Snapshot", "API error" ), - ( "Unpaused", "Migrated", "Snapshot" ), + ( "Paused", "Migrated", "IOError", "Watchdog", "Restored", "Snapshot", "API error", "Postcopy", "Postcopy failed" ), + ( "Unpaused", "Migrated", "Snapshot", "Postcopy" ), ( "Shutdown", "Destroyed", "Crashed", "Migrated", "Saved", "Failed", "Snapshot"), ( "Finished", "On guest request", "On host request"), ( "Memory", "Disk" ), -- 2.11.0

Catch exception if the event or details is outside the known type to prevent a traceback like the following:
raceback (most recent call last): File "/usr/lib/python2.7/dist-packages/libvirt.py", line 4601, in _dispatchDomainEventCallbacks cb(self, virDomain(self, _obj=dom), event, detail, opaque) File "libvirt-python/examples/event-test.py", line 505, in myDomainEventCallback1 domDetailToString(event, detail))) File "libvirt-python/examples/event-test.py", line 484, in domDetailToString return domEventStrings[event][detail] IndexError: tuple index out of range
Signed-off-by: Philipp Hahn <hahn@univention.de> --- 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 04310e1..4632110 100755 --- a/examples/event-test.py +++ b/examples/event-test.py @@ -467,7 +467,10 @@ def domEventToString(event): "PMSuspended", "Crashed", ) - return domEventStrings[event] + try: + return domEventStrings[event] + except IndexError: + return 'Unknown domain event %d' % (event,) def domDetailToString(event, detail): domEventStrings = ( @@ -481,7 +484,10 @@ def domDetailToString(event, detail): ( "Memory", "Disk" ), ( "Panicked", ), ) - return domEventStrings[event][detail] + try: + return domEventStrings[event][detail] + except IndexError: + return 'Unknown domain event %d detail %d' % (event, detail) def blockJobTypeToString(type): blockJobTypes = ( "unknown", "Pull", "Copy", "Commit", "ActiveCommit", ) -- 2.11.0

Hello, Am 20.09.18 um 08:10 schrieb Philipp Hahn:
event-test.py is bad at handling newer livecycle events. Attached are two patches to improve that.
Philipp Hahn (2): event-test.py: Sync list of domain lifecycle events event-test.py: Future proof lifecycle event handling
examples/event-test.py | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-)
While looking at the events reported by said script I noticed that there are no events for creating / deleting snapshot, at leat when the VM is inactive. For an active VM Qemu reports several events: VIR_DOMAIN_EVENT_DEFINED_FROM_SNAPSHOT VIR_DOMAIN_EVENT_STOPPED_FROM_SNAPSHOT VIR_DOMAIN_EVENT_STARTED_FROM_SNAPSHOT VIR_DOMAIN_EVENT_SUSPENDED_FROM_SNAPSHOT VIR_DOMAIN_EVENT_RESUMED_FROM_SNAPSHOT Also there is no event generated when doing "virsh change-media" for an inactive domain; I would have expected an VIR_DOMAIN_EVENT_DEFINED_UPDATED event. For an active domain you get the libvirt.VIR_DOMAIN_EVENT_TRAY_CHANGE_OPEN / CLOSE events. My current work-around is to do polling, but I want to get rid of that. Did I miss something or is it okay to add events for tha? Thanks in advance. Philipp

On Fri, Sep 21, 2018 at 08:39:20 +0200, Philipp Hahn wrote:
Hello,
Am 20.09.18 um 08:10 schrieb Philipp Hahn:
event-test.py is bad at handling newer livecycle events. Attached are two patches to improve that.
Philipp Hahn (2): event-test.py: Sync list of domain lifecycle events event-test.py: Future proof lifecycle event handling
examples/event-test.py | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-)
While looking at the events reported by said script I noticed that there are no events for creating / deleting snapshot, at leat when the VM is inactive. For an active VM Qemu reports several events: VIR_DOMAIN_EVENT_DEFINED_FROM_SNAPSHOT VIR_DOMAIN_EVENT_STOPPED_FROM_SNAPSHOT VIR_DOMAIN_EVENT_STARTED_FROM_SNAPSHOT VIR_DOMAIN_EVENT_SUSPENDED_FROM_SNAPSHOT VIR_DOMAIN_EVENT_RESUMED_FROM_SNAPSHOT
Also there is no event generated when doing "virsh change-media" for an inactive domain; I would have expected an VIR_DOMAIN_EVENT_DEFINED_UPDATED event. For an active domain you get the libvirt.VIR_DOMAIN_EVENT_TRAY_CHANGE_OPEN / CLOSE events.
My current work-around is to do polling, but I want to get rid of that. Did I miss something or is it okay to add events for tha?
The UPDATED event would make sense for both online/offline VMs, but it wasn't implemented there yet. Tray change events for offline VMs obviously don't make sense. Also for offline VMs the media change always succeeded if the API was successful.

On 09/20/2018 08:10 AM, Philipp Hahn wrote:
Hello,
event-test.py is bad at handling newer livecycle events. Attached are two patches to improve that.
Philipp Hahn (2): event-test.py: Sync list of domain lifecycle events event-test.py: Future proof lifecycle event handling
examples/event-test.py | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-)
Philipp, ACK to the first patch, no doubt about that. However, the second one - I'm torn. One one hand it makes the code more future proof, on the other - seeing a traceback (which is easy to spot) might inspire users to post patches (because we are obviously not doing a good job in keeping event-test.py in sync with libvirt). What do you think? Michal

Am 21.09.18 um 13:14 schrieb Michal Privoznik:
On 09/20/2018 08:10 AM, Philipp Hahn wrote:
event-test.py is bad at handling newer livecycle events. Attached are two patches to improve that.
Philipp Hahn (2): event-test.py: Sync list of domain lifecycle events event-test.py: Future proof lifecycle event handling
examples/event-test.py | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-)
ACK to the first patch, no doubt about that. However, the second one - I'm torn. One one hand it makes the code more future proof, on the other - seeing a traceback (which is easy to spot) might inspire users to post patches (because we are obviously not doing a good job in keeping event-test.py in sync with libvirt). What do you think?
Yes, I can understand it, but each time I use event-test.py (spaced months apart) I get the next crash. So I really would like it to be more future poof. If the number is annoying enough, any interested user is free so send a patch to improve it. As the pattern of printing some details is quiet common in that script, I did a v2 and added a somehow hacky class 'Description' to simplify writing those descriptive texts. Please have a look at the following series as an alternative. I also found some other issues I fixed along the way. Philipp Hahn (22): event-test.py: Handle closed connection event-test.py: Remove extra parenthesis event-test.py: Remove dead assignment event-test.py: Add missing globale statement event-test.py: Use __file__ event-test.py: Merge livecycle callbacks event-test.py: Simplify event ID lists event-test.py: Add class for event descriptions event-test.py: Convert LIVECYCLE events event-test.py: Convert BLOCKJOB events event-test.py: Convert WATCHDOG events event-test.py: Convert ERROR events event-test.py: Convert AGENT events event-test.py: Convert GRAPHICS events event-test.py: Convert DISK events event-test.py: Convert TRAY events event-test.py: Convert NETWORK events event-test.py: Convert STORAGE events event-test.py: Convert DEVICE events event-test.py: Convert SECRET events event-test.py: Convert CONNECTION events event-test.py: Fix blanks examples/event-test.py | 446 +++++++++++++++++++++++++++++-------------------- 1 file changed, 262 insertions(+), 184 deletions(-) -- 2.11.0

If libvirtd terminates while event-test.py has an open connection to it, it will crash with the following traceback:
myConnectionCloseCallback: qemu:///session: Error Exception in thread libvirtEventLoop: Traceback (most recent call last): File "/usr/lib/python2.7/threading.py", line 801, in __bootstrap_inner self.run() File "/usr/lib/python2.7/threading.py", line 754, in run self.__target(*self.__args, **self.__kwargs) File "examples/event-test.py", line 424, in virEventLoopPollRun eventLoop.run_loop() File "examples/event-test.py", line 242, in run_loop self.run_once() File "examples/event-test.py", line 187, in run_once libvirt.virEventInvokeFreeCallback(opaque) AttributeError: 'module' object has no attribute 'virEventInvokeFreeCallback'
libvirt: XML-RPC error : internal error: client socket is closed Traceback (most recent call last): File "examples/event-test.py", line 872, in <module> main() File "examples/event-test.py", line 854, in main vc.secretEventDeregisterAny(id) File "/usr/lib/python2.7/dist-packages/libvirt.py", line 4987, in secretEventDeregisterAny if ret == -1: raise libvirtError ('virConnectSecretEventDeregisterAny() failed', conn=self) libvirt.libvirtError: internal error: client socket is closed Closing qemu:///session
Skip unregistering the event callbacks and closing the connection if the connection is already broken / closed. Signed-off-by: Philipp Hahn <hahn@univention.de> --- examples/event-test.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/examples/event-test.py b/examples/event-test.py index 04310e1..c17d2bb 100755 --- a/examples/event-test.py +++ b/examples/event-test.py @@ -716,7 +716,8 @@ def main(): old_exitfunc = getattr(sys, 'exitfunc', None) def exit(): print("Closing " + vc.getURI()) - vc.close() + if run: + vc.close() if (old_exitfunc): old_exitfunc() sys.exitfunc = exit @@ -777,6 +778,11 @@ def main(): count = count + 1 time.sleep(1) + # If the connection was closed, we cannot unregister anything. + # Just abort now. + if not run: + return + vc.domainEventDeregister(myDomainEventCallback1) for id in seccallbacks: -- 2.11.0

Signed-off-by: Philipp Hahn <hahn@univention.de> --- examples/event-test.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/event-test.py b/examples/event-test.py index c17d2bb..a7c7054 100755 --- a/examples/event-test.py +++ b/examples/event-test.py @@ -224,7 +224,7 @@ class virEventLoopPoll: want = t.get_last_fired() + interval # Deduct 20ms, since scheduler timeslice # means we could be ever so slightly early - if now >= (want-20): + if now >= want - 20: debug("Dispatch timer %d now %s want %s" % (t.get_id(), str(now), str(want))) t.set_last_fired(now) t.dispatch() -- 2.11.0

variable is unused Signed-off-by: Philipp Hahn <hahn@univention.de> --- examples/event-test.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/event-test.py b/examples/event-test.py index a7c7054..1f34930 100755 --- a/examples/event-test.py +++ b/examples/event-test.py @@ -207,7 +207,7 @@ class virEventLoopPoll: # the data just continue if fd == self.pipetrick[0]: self.pendingWakeup = False - data = os.read(fd, 1) + os.read(fd, 1) continue h = self.get_handle_by_fd(fd) -- 2.11.0

to fix loop termination on exit. Signed-off-by: Philipp Hahn <hahn@univention.de> --- examples/event-test.py | 1 + 1 file changed, 1 insertion(+) diff --git a/examples/event-test.py b/examples/event-test.py index 1f34930..646ce50 100755 --- a/examples/event-test.py +++ b/examples/event-test.py @@ -663,6 +663,7 @@ def myConnectionCloseCallback(conn, reason, opaque): "Error", "End-of-file", "Keepalive", "Client", ) print("myConnectionCloseCallback: %s: %s" % (conn.getURI(), reasonStrings[reason])) + global run run = False def usage(): -- 2.11.0

instead of sys.argv[0] Signed-off-by: Philipp Hahn <hahn@univention.de> --- examples/event-test.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/event-test.py b/examples/event-test.py index 646ce50..ab1da4a 100755 --- a/examples/event-test.py +++ b/examples/event-test.py @@ -667,7 +667,7 @@ def myConnectionCloseCallback(conn, reason, opaque): run = False def usage(): - print("usage: "+os.path.basename(sys.argv[0])+" [-hdl] [uri]") + print("usage: %s [-hdl] [uri]" % (os.path.basename(__file__),)) print(" uri will default to qemu:///system") print(" --help, -h Print this help message") print(" --debug, -d Print debug output") -- 2.11.0

Registering the same function twice using the old domainEventRegister() interface would not work, as the function reference is used for un-registering. But it is not a problem with the new interface domainEventRegisterAn(), as that returns a unique ID. While at it also demonstrate the 'opaque' mechanism. Signed-off-by: Philipp Hahn <hahn@univention.de> --- examples/event-test.py | 18 +++++++----------- 1 file changed, 7 insertions(+), 11 deletions(-) diff --git a/examples/event-test.py b/examples/event-test.py index ab1da4a..2dcdee3 100755 --- a/examples/event-test.py +++ b/examples/event-test.py @@ -499,15 +499,11 @@ def agentLifecycleReasonToString(reason): agentReasons = ( "unknown", "domain started", "channel event", ) return agentReasons[reason] -def myDomainEventCallback1 (conn, dom, event, detail, opaque): - print("myDomainEventCallback1 EVENT: Domain %s(%s) %s %s" % (dom.name(), dom.ID(), - domEventToString(event), - domDetailToString(event, detail))) -def myDomainEventCallback2 (conn, dom, event, detail, opaque): - print("myDomainEventCallback2 EVENT: Domain %s(%s) %s %s" % (dom.name(), dom.ID(), - domEventToString(event), - domDetailToString(event, detail))) +def myDomainEventCallback(conn, dom, event, detail, opaque): + print("myDomainEventCallback%d EVENT: Domain %s(%s) %s %s" % ( + opaque, dom.name(), dom.ID(), domEventToString(event), domDetailToString(event, detail))) + def myDomainEventRebootCallback(conn, dom, opaque): print("myDomainEventRebootCallback: Domain %s(%s)" % (dom.name(), dom.ID())) @@ -725,9 +721,9 @@ def main(): vc.registerCloseCallback(myConnectionCloseCallback, None) #Add 2 lifecycle callbacks to prove this works with more than just one - vc.domainEventRegister(myDomainEventCallback1,None) + vc.domainEventRegister(myDomainEventCallback, 1) domcallbacks = [] - domcallbacks.append(vc.domainEventRegisterAny(None, libvirt.VIR_DOMAIN_EVENT_ID_LIFECYCLE, myDomainEventCallback2, None)) + domcallbacks.append(vc.domainEventRegisterAny(None, libvirt.VIR_DOMAIN_EVENT_ID_LIFECYCLE, myDomainEventCallback, 2)) 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)) @@ -784,7 +780,7 @@ def main(): if not run: return - vc.domainEventDeregister(myDomainEventCallback1) + vc.domainEventDeregister(myDomainEventCallback) for id in seccallbacks: vc.secretEventDeregisterAny(id) -- 2.11.0

by directly building the list with the IDs instead of appending them explicitly. Signed-off-by: Philipp Hahn <hahn@univention.de> --- examples/event-test.py | 87 ++++++++++++++++++++++++++------------------------ 1 file changed, 46 insertions(+), 41 deletions(-) diff --git a/examples/event-test.py b/examples/event-test.py index 2dcdee3..91a7cb7 100755 --- a/examples/event-test.py +++ b/examples/event-test.py @@ -722,47 +722,52 @@ def main(): #Add 2 lifecycle callbacks to prove this works with more than just one vc.domainEventRegister(myDomainEventCallback, 1) - domcallbacks = [] - domcallbacks.append(vc.domainEventRegisterAny(None, libvirt.VIR_DOMAIN_EVENT_ID_LIFECYCLE, myDomainEventCallback, 2)) - 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)) + domcallbacks = [ + vc.domainEventRegisterAny(None, libvirt.VIR_DOMAIN_EVENT_ID_LIFECYCLE, myDomainEventCallback, 2), + 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), + ] + + netcallbacks = [ + vc.networkEventRegisterAny(None, libvirt.VIR_NETWORK_EVENT_ID_LIFECYCLE, myNetworkEventLifecycleCallback, None), + ] + + poolcallbacks = [ + vc.storagePoolEventRegisterAny(None, libvirt.VIR_STORAGE_POOL_EVENT_ID_LIFECYCLE, myStoragePoolEventLifecycleCallback, None), + vc.storagePoolEventRegisterAny(None, libvirt.VIR_STORAGE_POOL_EVENT_ID_REFRESH, myStoragePoolEventRefreshCallback, None), + ] + + devcallbacks = [ + vc.nodeDeviceEventRegisterAny(None, libvirt.VIR_NODE_DEVICE_EVENT_ID_LIFECYCLE, myNodeDeviceEventLifecycleCallback, None), + vc.nodeDeviceEventRegisterAny(None, libvirt.VIR_NODE_DEVICE_EVENT_ID_UPDATE, myNodeDeviceEventUpdateCallback, None), + ] + + seccallbacks = [ + vc.secretEventRegisterAny(None, libvirt.VIR_SECRET_EVENT_ID_LIFECYCLE, mySecretEventLifecycleCallback, None), + vc.secretEventRegisterAny(None, libvirt.VIR_SECRET_EVENT_ID_VALUE_CHANGED, mySecretEventValueChanged, None), + ] vc.setKeepAlive(5, 3) -- 2.11.0

Signed-off-by: Philipp Hahn <hahn@univention.de> --- examples/event-test.py | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/examples/event-test.py b/examples/event-test.py index 91a7cb7..d2d2c60 100755 --- a/examples/event-test.py +++ b/examples/event-test.py @@ -456,6 +456,31 @@ def virEventLoopNativeStart(): ########################################################################## # Everything that now follows is a simple demo of domain lifecycle events ########################################################################## +class Description(object): + __slots__ = ('desc', 'args') + + def __init__(self, *args, **kwargs): + self.desc = kwargs.get('desc') + self.args = args + + def __str__(self): # type: () -> str + return self.desc + + def __getitem__(self, item): # type: (int) -> str + try: + data = self.args[item] + except IndexError: + return self.__class__(desc=str(item)) + + if isinstance(data, str): + return self.__class__(desc=data) + elif isinstance(data, (list, tuple)): + desc, args = data + return self.__class__(*args, desc=desc) + + raise TypeError(args) + + def domEventToString(event): domEventStrings = ( "Defined", "Undefined", -- 2.11.0

to use new Description class Signed-off-by: Philipp Hahn <hahn@univention.de> --- examples/event-test.py | 42 ++++++++++++++---------------------------- 1 file changed, 14 insertions(+), 28 deletions(-) diff --git a/examples/event-test.py b/examples/event-test.py index d2d2c60..493828f 100755 --- a/examples/event-test.py +++ b/examples/event-test.py @@ -481,32 +481,18 @@ class Description(object): raise TypeError(args) -def domEventToString(event): - domEventStrings = ( "Defined", - "Undefined", - "Started", - "Suspended", - "Resumed", - "Stopped", - "Shutdown", - "PMSuspended", - "Crashed", - ) - return domEventStrings[event] - -def domDetailToString(event, detail): - domEventStrings = ( - ( "Added", "Updated", "Renamed", "Snapshot" ), - ( "Removed", "Renamed", ), - ( "Booted", "Migrated", "Restored", "Snapshot", "Wakeup" ), - ( "Paused", "Migrated", "IOError", "Watchdog", "Restored", "Snapshot", "API error", "Postcopy", "Postcopy failed" ), - ( "Unpaused", "Migrated", "Snapshot", "Postcopy" ), - ( "Shutdown", "Destroyed", "Crashed", "Migrated", "Saved", "Failed", "Snapshot"), - ( "Finished", "On guest request", "On host request"), - ( "Memory", "Disk" ), - ( "Panicked", ), - ) - return domEventStrings[event][detail] +DOM_EVENTS = Description( + ("Defined", ("Added", "Updated", "Renamed", "Snapshot")), + ("Undefined", ("Removed", "Renamed")), + ("Started", ("Booted", "Migrated", "Restored", "Snapshot", "Wakeup")), + ("Suspended", ("Paused", "Migrated", "IOError", "Watchdog", "Restored", "Snapshot", "API error", "Postcopy", "Postcopy failed")), + ("Resumed", ("Unpaused", "Migrated", "Snapshot", "Postcopy")), + ("Stopped", ("Shutdown", "Destroyed", "Crashed", "Migrated", "Saved", "Failed", "Snapshot")), + ("Shutdown", ("Finished", "On guest request", "On host request")), + ("PMSuspended", ("Memory", "Disk")), + ("Crashed", ("Panicked",)), +) + def blockJobTypeToString(type): blockJobTypes = ( "unknown", "Pull", "Copy", "Commit", "ActiveCommit", ) @@ -526,8 +512,8 @@ def agentLifecycleReasonToString(reason): def myDomainEventCallback(conn, dom, event, detail, opaque): - print("myDomainEventCallback%d EVENT: Domain %s(%s) %s %s" % ( - opaque, dom.name(), dom.ID(), domEventToString(event), domDetailToString(event, detail))) + print("myDomainEventCallback%s EVENT: Domain %s(%s) %s %s" % ( + opaque, dom.name(), dom.ID(), DOM_EVENTS[event], DOM_EVENTS[event][detail])) def myDomainEventRebootCallback(conn, dom, opaque): -- 2.11.0

to use new Description class Signed-off-by: Philipp Hahn <hahn@univention.de> --- examples/event-test.py | 25 ++++++++++++++----------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/examples/event-test.py b/examples/event-test.py index 493828f..46a8db8 100755 --- a/examples/event-test.py +++ b/examples/event-test.py @@ -492,15 +492,8 @@ DOM_EVENTS = Description( ("PMSuspended", ("Memory", "Disk")), ("Crashed", ("Panicked",)), ) - - -def blockJobTypeToString(type): - blockJobTypes = ( "unknown", "Pull", "Copy", "Commit", "ActiveCommit", ) - return blockJobTypes[type] - -def blockJobStatusToString(status): - blockJobStatus = ( "Completed", "Failed", "Canceled", "Ready", ) - return blockJobStatus[status] +BLOCK_JOB_TYPES = Description("unknown", "Pull", "Copy", "Commit", "ActiveCommit") +BLOCK_JOB_STATUS = Description("Completed", "Failed", "Canceled", "Ready") def agentLifecycleStateToString(state): agentStates = ( "unknown", "connected", "disconnected", ) @@ -533,8 +526,13 @@ def myDomainEventGraphicsCallback(conn, dom, phase, localAddr, remoteAddr, authS print("myDomainEventGraphicsCallback: Domain %s(%s) %d %s" % (dom.name(), dom.ID(), phase, authScheme)) def myDomainEventControlErrorCallback(conn, dom, opaque): print("myDomainEventControlErrorCallback: Domain %s(%s)" % (dom.name(), dom.ID())) + + def myDomainEventBlockJobCallback(conn, dom, disk, type, status, opaque): - print("myDomainEventBlockJobCallback: Domain %s(%s) %s on disk %s %s" % (dom.name(), dom.ID(), blockJobTypeToString(type), disk, blockJobStatusToString(status))) + print("myDomainEventBlockJobCallback: Domain %s(%s) %s on disk %s %s" % ( + dom.name(), dom.ID(), BLOCK_JOB_TYPES[type], disk, BLOCK_JOB_STATUS[status])) + + def myDomainEventDiskChangeCallback(conn, dom, oldSrcPath, newSrcPath, devAlias, reason, opaque): print("myDomainEventDiskChangeCallback: Domain %s(%s) disk change oldSrcPath: %s newSrcPath: %s devAlias: %s reason: %s" % ( dom.name(), dom.ID(), oldSrcPath, newSrcPath, devAlias, reason)) @@ -555,8 +553,13 @@ def myDomainEventPMSuspendDiskCallback(conn, dom, reason, opaque): def myDomainEventDeviceRemovedCallback(conn, dom, dev, opaque): print("myDomainEventDeviceRemovedCallback: Domain %s(%s) device removed: %s" % ( dom.name(), dom.ID(), dev)) + + def myDomainEventBlockJob2Callback(conn, dom, disk, type, status, opaque): - print("myDomainEventBlockJob2Callback: Domain %s(%s) %s on disk %s %s" % (dom.name(), dom.ID(), blockJobTypeToString(type), disk, blockJobStatusToString(status))) + print("myDomainEventBlockJob2Callback: Domain %s(%s) %s on disk %s %s" % ( + dom.name(), dom.ID(), BLOCK_JOB_TYPES[type], disk, BLOCK_JOB_STATUS[status])) + + def myDomainEventTunableCallback(conn, dom, params, opaque): print("myDomainEventTunableCallback: Domain %s(%s) %s" % (dom.name(), dom.ID(), params)) def myDomainEventAgentLifecycleCallback(conn, dom, state, reason, opaque): -- 2.11.0

to use new Description class Signed-off-by: Philipp Hahn <hahn@univention.de> --- examples/event-test.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/examples/event-test.py b/examples/event-test.py index 46a8db8..004a263 100755 --- a/examples/event-test.py +++ b/examples/event-test.py @@ -494,6 +494,7 @@ DOM_EVENTS = Description( ) BLOCK_JOB_TYPES = Description("unknown", "Pull", "Copy", "Commit", "ActiveCommit") BLOCK_JOB_STATUS = Description("Completed", "Failed", "Canceled", "Ready") +WATCHDOG_ACTIONS = Description("none", "Pause", "Reset", "Poweroff", "Shutdown", "Debug", "Inject NMI") def agentLifecycleStateToString(state): agentStates = ( "unknown", "connected", "disconnected", ) @@ -515,8 +516,11 @@ def myDomainEventRebootCallback(conn, dom, opaque): def myDomainEventRTCChangeCallback(conn, dom, utcoffset, opaque): print("myDomainEventRTCChangeCallback: Domain %s(%s) %d" % (dom.name(), dom.ID(), utcoffset)) + def myDomainEventWatchdogCallback(conn, dom, action, opaque): - print("myDomainEventWatchdogCallback: Domain %s(%s) %d" % (dom.name(), dom.ID(), action)) + print("myDomainEventWatchdogCallback: Domain %s(%s) %s" % ( + dom.name(), dom.ID(), WATCHDOG_ACTIONS[action])) + def myDomainEventIOErrorCallback(conn, dom, srcpath, devalias, action, opaque): print("myDomainEventIOErrorCallback: Domain %s(%s) %s %s %d" % (dom.name(), dom.ID(), srcpath, devalias, action)) -- 2.11.0

to use new Description class Signed-off-by: Philipp Hahn <hahn@univention.de> --- examples/event-test.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/examples/event-test.py b/examples/event-test.py index 004a263..4a801b7 100755 --- a/examples/event-test.py +++ b/examples/event-test.py @@ -495,6 +495,7 @@ DOM_EVENTS = Description( BLOCK_JOB_TYPES = Description("unknown", "Pull", "Copy", "Commit", "ActiveCommit") BLOCK_JOB_STATUS = Description("Completed", "Failed", "Canceled", "Ready") WATCHDOG_ACTIONS = Description("none", "Pause", "Reset", "Poweroff", "Shutdown", "Debug", "Inject NMI") +ERROR_EVENTS = Description("None", "Pause", "Report") def agentLifecycleStateToString(state): agentStates = ( "unknown", "connected", "disconnected", ) @@ -524,8 +525,13 @@ def myDomainEventWatchdogCallback(conn, dom, action, opaque): def myDomainEventIOErrorCallback(conn, dom, srcpath, devalias, action, opaque): print("myDomainEventIOErrorCallback: Domain %s(%s) %s %s %d" % (dom.name(), dom.ID(), srcpath, devalias, action)) + + def myDomainEventIOErrorReasonCallback(conn, dom, srcpath, devalias, action, reason, opaque): - print("myDomainEventIOErrorReasonCallback: Domain %s(%s) %s %s %d %s" % (dom.name(), dom.ID(), srcpath, devalias, action, reason)) + print("myDomainEventIOErrorReasonCallback: Domain %s(%s) %s %s %d %s" % ( + dom.name(), dom.ID(), srcpath, devalias, action, ERROR_EVENTS[reason])) + + def myDomainEventGraphicsCallback(conn, dom, phase, localAddr, remoteAddr, authScheme, subject, opaque): print("myDomainEventGraphicsCallback: Domain %s(%s) %d %s" % (dom.name(), dom.ID(), phase, authScheme)) def myDomainEventControlErrorCallback(conn, dom, opaque): -- 2.11.0

to use new Description class Signed-off-by: Philipp Hahn <hahn@univention.de> --- examples/event-test.py | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/examples/event-test.py b/examples/event-test.py index 4a801b7..b559ede 100755 --- a/examples/event-test.py +++ b/examples/event-test.py @@ -496,14 +496,8 @@ BLOCK_JOB_TYPES = Description("unknown", "Pull", "Copy", "Commit", "ActiveCommit BLOCK_JOB_STATUS = Description("Completed", "Failed", "Canceled", "Ready") WATCHDOG_ACTIONS = Description("none", "Pause", "Reset", "Poweroff", "Shutdown", "Debug", "Inject NMI") ERROR_EVENTS = Description("None", "Pause", "Report") - -def agentLifecycleStateToString(state): - agentStates = ( "unknown", "connected", "disconnected", ) - return agentStates[state] - -def agentLifecycleReasonToString(reason): - agentReasons = ( "unknown", "domain started", "channel event", ) - return agentReasons[reason] +AGENT_STATES = Description("unknown", "connected", "disconnected") +AGENT_REASONS = Description("unknown", "domain started", "channel event") def myDomainEventCallback(conn, dom, event, detail, opaque): @@ -572,8 +566,13 @@ def myDomainEventBlockJob2Callback(conn, dom, disk, type, status, opaque): def myDomainEventTunableCallback(conn, dom, params, opaque): print("myDomainEventTunableCallback: Domain %s(%s) %s" % (dom.name(), dom.ID(), params)) + + def myDomainEventAgentLifecycleCallback(conn, dom, state, reason, opaque): - print("myDomainEventAgentLifecycleCallback: Domain %s(%s) %s %s" % (dom.name(), dom.ID(), agentLifecycleStateToString(state), agentLifecycleReasonToString(reason))) + print("myDomainEventAgentLifecycleCallback: Domain %s(%s) %s %s" % ( + dom.name(), dom.ID(), AGENT_STATES[state], AGENT_REASONS[reason])) + + def myDomainEventDeviceAddedCallback(conn, dom, dev, opaque): print("myDomainEventDeviceAddedCallback: Domain %s(%s) device added: %s" % ( dom.name(), dom.ID(), dev)) -- 2.11.0

to use new Description class Signed-off-by: Philipp Hahn <hahn@univention.de> --- examples/event-test.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/examples/event-test.py b/examples/event-test.py index b559ede..d8ba8c9 100755 --- a/examples/event-test.py +++ b/examples/event-test.py @@ -498,6 +498,7 @@ WATCHDOG_ACTIONS = Description("none", "Pause", "Reset", "Poweroff", "Shutdown", ERROR_EVENTS = Description("None", "Pause", "Report") AGENT_STATES = Description("unknown", "connected", "disconnected") AGENT_REASONS = Description("unknown", "domain started", "channel event") +GRAPHICS_PHASES = Description("Connect", "Initialize", "Disconnect") def myDomainEventCallback(conn, dom, event, detail, opaque): @@ -527,7 +528,10 @@ def myDomainEventIOErrorReasonCallback(conn, dom, srcpath, devalias, action, rea def myDomainEventGraphicsCallback(conn, dom, phase, localAddr, remoteAddr, authScheme, subject, opaque): - print("myDomainEventGraphicsCallback: Domain %s(%s) %d %s" % (dom.name(), dom.ID(), phase, authScheme)) + print("myDomainEventGraphicsCallback: Domain %s(%s) %s %s" % ( + dom.name(), dom.ID(), GRAPHICS_PHASES[phase], authScheme)) + + def myDomainEventControlErrorCallback(conn, dom, opaque): print("myDomainEventControlErrorCallback: Domain %s(%s)" % (dom.name(), dom.ID())) -- 2.11.0

to use new Description class Signed-off-by: Philipp Hahn <hahn@univention.de> --- examples/event-test.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/examples/event-test.py b/examples/event-test.py index d8ba8c9..218103d 100755 --- a/examples/event-test.py +++ b/examples/event-test.py @@ -499,6 +499,7 @@ ERROR_EVENTS = Description("None", "Pause", "Report") AGENT_STATES = Description("unknown", "connected", "disconnected") AGENT_REASONS = Description("unknown", "domain started", "channel event") GRAPHICS_PHASES = Description("Connect", "Initialize", "Disconnect") +DISK_EVENTS = Description("Change missing on start", "Drop missing on start") def myDomainEventCallback(conn, dom, event, detail, opaque): @@ -543,7 +544,9 @@ def myDomainEventBlockJobCallback(conn, dom, disk, type, status, opaque): def myDomainEventDiskChangeCallback(conn, dom, oldSrcPath, newSrcPath, devAlias, reason, opaque): print("myDomainEventDiskChangeCallback: Domain %s(%s) disk change oldSrcPath: %s newSrcPath: %s devAlias: %s reason: %s" % ( - dom.name(), dom.ID(), oldSrcPath, newSrcPath, devAlias, reason)) + dom.name(), dom.ID(), oldSrcPath, newSrcPath, devAlias, DISK_EVENTS[reason])) + + def myDomainEventTrayChangeCallback(conn, dom, devAlias, reason, opaque): print("myDomainEventTrayChangeCallback: Domain %s(%s) tray change devAlias: %s reason: %s" % ( dom.name(), dom.ID(), devAlias, reason)) -- 2.11.0

to use new Description class Signed-off-by: Philipp Hahn <hahn@univention.de> --- examples/event-test.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/examples/event-test.py b/examples/event-test.py index 218103d..5426ecd 100755 --- a/examples/event-test.py +++ b/examples/event-test.py @@ -500,6 +500,7 @@ AGENT_STATES = Description("unknown", "connected", "disconnected") AGENT_REASONS = Description("unknown", "domain started", "channel event") GRAPHICS_PHASES = Description("Connect", "Initialize", "Disconnect") DISK_EVENTS = Description("Change missing on start", "Drop missing on start") +TRAY_EVENTS = Description("Opened", "Closed") def myDomainEventCallback(conn, dom, event, detail, opaque): @@ -549,7 +550,9 @@ def myDomainEventDiskChangeCallback(conn, dom, oldSrcPath, newSrcPath, devAlias, def myDomainEventTrayChangeCallback(conn, dom, devAlias, reason, opaque): print("myDomainEventTrayChangeCallback: Domain %s(%s) tray change devAlias: %s reason: %s" % ( - dom.name(), dom.ID(), devAlias, reason)) + dom.name(), dom.ID(), devAlias, TRAY_EVENTS[reason])) + + def myDomainEventPMWakeupCallback(conn, dom, reason, opaque): print("myDomainEventPMWakeupCallback: Domain %s(%s) system pmwakeup" % ( dom.name(), dom.ID())) -- 2.11.0

to use new Description class Signed-off-by: Philipp Hahn <hahn@univention.de> --- examples/event-test.py | 29 ++++++++++------------------- 1 file changed, 10 insertions(+), 19 deletions(-) diff --git a/examples/event-test.py b/examples/event-test.py index 5426ecd..2436827 100755 --- a/examples/event-test.py +++ b/examples/event-test.py @@ -604,27 +604,18 @@ def myDomainEventBlockThresholdCallback(conn, dom, dev, path, threshold, excess, ########################################################################## # Network events ########################################################################## -def netEventToString(event): - netEventStrings = ( "Defined", - "Undefined", - "Started", - "Stopped", - ) - return netEventStrings[event] - -def netDetailToString(event, detail): - netEventStrings = ( - ( "Added", ), - ( "Removed", ), - ( "Started", ), - ( "Stopped", ), - ) - return netEventStrings[event][detail] +NET_EVENTS = Description( + ("Defined", ("Added",)), + ("Undefined", ("Removed",)), + ("Started", ("Started",)), + ("Stopped", ("Stopped",)), +) + def myNetworkEventLifecycleCallback(conn, net, event, detail, opaque): - print("myNetworkEventLifecycleCallback: Network %s %s %s" % (net.name(), - netEventToString(event), - netDetailToString(event, detail))) + print("myNetworkEventLifecycleCallback: Network %s %s %s" % ( + net.name(), NET_EVENTS[event], NET_EVENTS[event][detail])) + ########################################################################## # Storage pool events -- 2.11.0

to use new Description class Signed-off-by: Philipp Hahn <hahn@univention.de> --- examples/event-test.py | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/examples/event-test.py b/examples/event-test.py index 2436827..499f434 100755 --- a/examples/event-test.py +++ b/examples/event-test.py @@ -620,20 +620,20 @@ def myNetworkEventLifecycleCallback(conn, net, event, detail, opaque): ########################################################################## # Storage pool events ########################################################################## -def storageEventToString(event): - storageEventStrings = ( "Defined", - "Undefined", - "Started", - "Stopped", - "Created", - "Deleted", - ) - return storageEventStrings[event] +STORAGE_EVENTS = Description( + ("Defined", ()), + ("Undefined", ()), + ("Started", ()), + ("Stopped", ()), + ("Created", ()), + ("Deleted", ()), +) + def myStoragePoolEventLifecycleCallback(conn, pool, event, detail, opaque): - print("myStoragePoolEventLifecycleCallback: Storage pool %s %s %d" % (pool.name(), - storageEventToString(event), - detail)) + print("myStoragePoolEventLifecycleCallback: Storage pool %s %s %s" % ( + pool.name(), STORAGE_EVENTS[event], STORAGE_EVENTS[event][detail])) + def myStoragePoolEventRefreshCallback(conn, pool, opaque): print("myStoragePoolEventRefreshCallback: Storage pool %s" % pool.name()) -- 2.11.0

to use new Description class Signed-off-by: Philipp Hahn <hahn@univention.de> --- examples/event-test.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/examples/event-test.py b/examples/event-test.py index 499f434..0a1d06d 100755 --- a/examples/event-test.py +++ b/examples/event-test.py @@ -641,16 +641,16 @@ def myStoragePoolEventRefreshCallback(conn, pool, opaque): ########################################################################## # Node device events ########################################################################## -def nodeDeviceEventToString(event): - nodeDeviceEventStrings = ( "Created", - "Deleted", - ) - return nodeDeviceEventStrings[event] +DEVICE_EVENTS = Description( + ("Created", ()), + ("Deleted", ()), +) + def myNodeDeviceEventLifecycleCallback(conn, dev, event, detail, opaque): - print("myNodeDeviceEventLifecycleCallback: Node device %s %s %d" % (dev.name(), - nodeDeviceEventToString(event), - detail)) + print("myNodeDeviceEventLifecycleCallback: Node device %s %s %s" % ( + dev.name(), DEVICE_EVENTS[event], DEVICE_EVENTS[event][detail])) + def myNodeDeviceEventUpdateCallback(conn, dev, opaque): print("myNodeDeviceEventUpdateCallback: Node device %s" % dev.name()) -- 2.11.0

to use new Description class Signed-off-by: Philipp Hahn <hahn@univention.de> --- examples/event-test.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/examples/event-test.py b/examples/event-test.py index 0a1d06d..5e3b884 100755 --- a/examples/event-test.py +++ b/examples/event-test.py @@ -658,16 +658,16 @@ def myNodeDeviceEventUpdateCallback(conn, dev, opaque): ########################################################################## # Secret events ########################################################################## -def secretEventToString(event): - secretEventStrings = ( "Defined", - "Undefined", - ) - return secretEventStrings[event] +SECRET_EVENTS = Description( + ("Defined", ()), + ("Undefined", ()), +) + def mySecretEventLifecycleCallback(conn, secret, event, detail, opaque): - print("mySecretEventLifecycleCallback: Secret %s %s %d" % (secret.UUIDString(), - secretEventToString(event), - detail)) + print("mySecretEventLifecycleCallback: Secret %s %s %s" % ( + secret.UUIDString(), SECRET_EVENTS[event], SECRET_EVENTS[event][detail])) + def mySecretEventValueChanged(conn, secret, opaque): print("mySecretEventValueChanged: Secret %s" % secret.UUIDString()) -- 2.11.0

to use new Description class Signed-off-by: Philipp Hahn <hahn@univention.de> --- examples/event-test.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/examples/event-test.py b/examples/event-test.py index 5e3b884..1e94838 100755 --- a/examples/event-test.py +++ b/examples/event-test.py @@ -677,15 +677,16 @@ def mySecretEventValueChanged(conn, secret, opaque): ########################################################################## run = True +CONNECTION_EVENTS = Description("Error", "End-of-file", "Keepalive", "Client") + def myConnectionCloseCallback(conn, reason, opaque): - reasonStrings = ( - "Error", "End-of-file", "Keepalive", "Client", - ) - print("myConnectionCloseCallback: %s: %s" % (conn.getURI(), reasonStrings[reason])) + print("myConnectionCloseCallback: %s: %s" % ( + conn.getURI(), CONNECTION_EVENTS[reason])) global run run = False + def usage(): print("usage: %s [-hdl] [uri]" % (os.path.basename(__file__),)) print(" uri will default to qemu:///system") -- 2.11.0

Closer to pep8 Signed-off-by: Philipp Hahn <hahn@univention.de> --- examples/event-test.py | 97 ++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 71 insertions(+), 26 deletions(-) diff --git a/examples/event-test.py b/examples/event-test.py index 1e94838..540bf9b 100755 --- a/examples/event-test.py +++ b/examples/event-test.py @@ -30,11 +30,14 @@ import threading event_impl = "poll" do_debug = False + + def debug(msg): global do_debug if do_debug: print(msg) + # # This general purpose event loop will support waiting for file handle # I/O and errors events, as well as scheduling repeatable timers with @@ -100,7 +103,6 @@ class virEventLoopPoll: self.cb(self.timer, self.opaque) - def __init__(self): self.poll = select.poll() self.pipetrick = os.pipe() @@ -126,10 +128,9 @@ class virEventLoopPoll: # with the event loop for input events. When we need to force # the main thread out of a poll() sleep, we simple write a # single byte of data to the other end of the pipe. - debug("Self pipe watch %d write %d" %(self.pipetrick[0], self.pipetrick[1])) + debug("Self pipe watch %d write %d" % (self.pipetrick[0], self.pipetrick[1])) self.poll.register(self.pipetrick[0], select.POLLIN) - # Calculate when the next timeout is due to occur, returning # the absolute timestamp for the next timeout, or 0 if there is # no timeout due @@ -159,7 +160,6 @@ class virEventLoopPoll: return h return None - # This is the heart of the event loop, performing one single # iteration. It asks when the next timeout is due, and then # calculates the maximum amount of time it is able to sleep @@ -235,7 +235,6 @@ class virEventLoopPoll: finally: self.runningPoll = False - # Actually run the event loop forever def run_loop(self): self.quit = False @@ -247,7 +246,6 @@ class virEventLoopPoll: self.pendingWakeup = True os.write(self.pipetrick[1], 'c'.encode("UTF-8")) - # Registers a new file handle 'fd', monitoring for 'events' (libvirt # event constants), firing the callback cb() when an event occurs. # Returns a unique integer identier for this handle, that should be @@ -301,7 +299,7 @@ class virEventLoopPoll: h.set_interval(interval) self.interrupt() - debug("Update timer %d interval %d" % (timerID, interval)) + debug("Update timer %d interval %d" % (timerID, interval)) break # Stop monitoring for events on the file handle @@ -383,26 +381,32 @@ def virEventAddHandleImpl(fd, events, cb, opaque): global eventLoop return eventLoop.add_handle(fd, events, cb, opaque) + def virEventUpdateHandleImpl(handleID, events): global eventLoop return eventLoop.update_handle(handleID, events) + def virEventRemoveHandleImpl(handleID): global eventLoop return eventLoop.remove_handle(handleID) + def virEventAddTimerImpl(interval, cb, opaque): global eventLoop return eventLoop.add_timer(interval, cb, opaque) + def virEventUpdateTimerImpl(timerID, interval): global eventLoop return eventLoop.update_timer(timerID, interval) + def virEventRemoveTimerImpl(timerID): global eventLoop return eventLoop.remove_timer(timerID) + # This tells libvirt what event loop implementation it # should use def virEventLoopPollRegister(): @@ -413,20 +417,24 @@ def virEventLoopPollRegister(): virEventUpdateTimerImpl, virEventRemoveTimerImpl) + # Directly run the event loop in the current thread 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() + # Spawn a background thread to run the event loop def virEventLoopPollStart(): global eventLoopThread @@ -435,6 +443,7 @@ def virEventLoopPollStart(): eventLoopThread.setDaemon(True) eventLoopThread.start() + def virEventLoopAIOStart(): global eventLoopThread import libvirtaio @@ -445,6 +454,7 @@ def virEventLoopAIOStart(): eventLoopThread.setDaemon(True) eventLoopThread.start() + def virEventLoopNativeStart(): global eventLoopThread libvirt.virEventRegisterDefaultImpl() @@ -509,10 +519,13 @@ def myDomainEventCallback(conn, dom, event, detail, opaque): def myDomainEventRebootCallback(conn, dom, opaque): - print("myDomainEventRebootCallback: Domain %s(%s)" % (dom.name(), dom.ID())) + print("myDomainEventRebootCallback: Domain %s(%s)" % ( + dom.name(), dom.ID())) + def myDomainEventRTCChangeCallback(conn, dom, utcoffset, opaque): - print("myDomainEventRTCChangeCallback: Domain %s(%s) %d" % (dom.name(), dom.ID(), utcoffset)) + print("myDomainEventRTCChangeCallback: Domain %s(%s) %d" % ( + dom.name(), dom.ID(), utcoffset)) def myDomainEventWatchdogCallback(conn, dom, action, opaque): @@ -521,7 +534,8 @@ def myDomainEventWatchdogCallback(conn, dom, action, opaque): def myDomainEventIOErrorCallback(conn, dom, srcpath, devalias, action, opaque): - print("myDomainEventIOErrorCallback: Domain %s(%s) %s %s %d" % (dom.name(), dom.ID(), srcpath, devalias, action)) + print("myDomainEventIOErrorCallback: Domain %s(%s) %s %s %d" % ( + dom.name(), dom.ID(), srcpath, devalias, action)) def myDomainEventIOErrorReasonCallback(conn, dom, srcpath, devalias, action, reason, opaque): @@ -535,7 +549,8 @@ def myDomainEventGraphicsCallback(conn, dom, phase, localAddr, remoteAddr, authS def myDomainEventControlErrorCallback(conn, dom, opaque): - print("myDomainEventControlErrorCallback: Domain %s(%s)" % (dom.name(), dom.ID())) + print("myDomainEventControlErrorCallback: Domain %s(%s)" % ( + dom.name(), dom.ID())) def myDomainEventBlockJobCallback(conn, dom, disk, type, status, opaque): @@ -555,18 +570,27 @@ def myDomainEventTrayChangeCallback(conn, dom, devAlias, reason, opaque): def myDomainEventPMWakeupCallback(conn, dom, reason, opaque): print("myDomainEventPMWakeupCallback: Domain %s(%s) system pmwakeup" % ( - dom.name(), dom.ID())) + dom.name(), dom.ID())) + + def myDomainEventPMSuspendCallback(conn, dom, reason, opaque): print("myDomainEventPMSuspendCallback: Domain %s(%s) system pmsuspend" % ( - dom.name(), dom.ID())) + dom.name(), dom.ID())) + + def myDomainEventBalloonChangeCallback(conn, dom, actual, opaque): - print("myDomainEventBalloonChangeCallback: Domain %s(%s) %d" % (dom.name(), dom.ID(), actual)) + print("myDomainEventBalloonChangeCallback: Domain %s(%s) %d" % ( + dom.name(), dom.ID(), actual)) + + def myDomainEventPMSuspendDiskCallback(conn, dom, reason, opaque): print("myDomainEventPMSuspendDiskCallback: Domain %s(%s) system pmsuspend_disk" % ( - dom.name(), dom.ID())) + dom.name(), dom.ID())) + + def myDomainEventDeviceRemovedCallback(conn, dom, dev, opaque): print("myDomainEventDeviceRemovedCallback: Domain %s(%s) device removed: %s" % ( - dom.name(), dom.ID(), dev)) + dom.name(), dom.ID(), dev)) def myDomainEventBlockJob2Callback(conn, dom, disk, type, status, opaque): @@ -575,7 +599,8 @@ def myDomainEventBlockJob2Callback(conn, dom, disk, type, status, opaque): def myDomainEventTunableCallback(conn, dom, params, opaque): - print("myDomainEventTunableCallback: Domain %s(%s) %s" % (dom.name(), dom.ID(), params)) + print("myDomainEventTunableCallback: Domain %s(%s) %s" % ( + dom.name(), dom.ID(), params)) def myDomainEventAgentLifecycleCallback(conn, dom, state, reason, opaque): @@ -585,21 +610,33 @@ def myDomainEventAgentLifecycleCallback(conn, dom, state, reason, opaque): def myDomainEventDeviceAddedCallback(conn, dom, dev, opaque): print("myDomainEventDeviceAddedCallback: Domain %s(%s) device added: %s" % ( - dom.name(), dom.ID(), dev)) + dom.name(), dom.ID(), dev)) + + def myDomainEventMigrationIteration(conn, dom, iteration, opaque): print("myDomainEventMigrationIteration: Domain %s(%s) started migration iteration %d" % ( - dom.name(), dom.ID(), iteration)) + dom.name(), dom.ID(), iteration)) + + def myDomainEventJobCompletedCallback(conn, dom, params, opaque): - print("myDomainEventJobCompletedCallback: Domain %s(%s) %s" % (dom.name(), dom.ID(), params)) + print("myDomainEventJobCompletedCallback: Domain %s(%s) %s" % ( + dom.name(), dom.ID(), params)) + + def myDomainEventDeviceRemovalFailedCallback(conn, dom, dev, opaque): print("myDomainEventDeviceRemovalFailedCallback: Domain %s(%s) failed to remove device: %s" % ( - dom.name(), dom.ID(), dev)) + dom.name(), dom.ID(), dev)) + + def myDomainEventMetadataChangeCallback(conn, dom, mtype, nsuri, opaque): print("myDomainEventMetadataChangeCallback: Domain %s(%s) changed metadata mtype=%d nsuri=%s" % ( - dom.name(), dom.ID(), mtype, nsuri)) + dom.name(), dom.ID(), mtype, nsuri)) + + def myDomainEventBlockThresholdCallback(conn, dom, dev, path, threshold, excess, opaque): print("myDomainEventBlockThresholdCallback: Domain %s(%s) block device %s(%s) threshold %d exceeded by %d" % ( - dom.name(), dom.ID(), dev, path, threshold, excess)) + dom.name(), dom.ID(), dev, path, threshold, excess)) + ########################################################################## # Network events @@ -638,6 +675,7 @@ def myStoragePoolEventLifecycleCallback(conn, pool, event, detail, opaque): def myStoragePoolEventRefreshCallback(conn, pool, opaque): print("myStoragePoolEventRefreshCallback: Storage pool %s" % pool.name()) + ########################################################################## # Node device events ########################################################################## @@ -655,6 +693,7 @@ def myNodeDeviceEventLifecycleCallback(conn, dev, event, detail, opaque): def myNodeDeviceEventUpdateCallback(conn, dev, opaque): print("myNodeDeviceEventUpdateCallback: Node device %s" % dev.name()) + ########################################################################## # Secret events ########################################################################## @@ -672,6 +711,7 @@ def mySecretEventLifecycleCallback(conn, secret, event, detail, opaque): def mySecretEventValueChanged(conn, secret, opaque): print("mySecretEventValueChanged: Secret %s" % secret.UUIDString()) + ########################################################################## # Set up and run the program ########################################################################## @@ -695,12 +735,13 @@ def usage(): 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="]) except getopt.GetoptError as err: # print help information and exit: - print(str(err)) # will print something like "option -a not recognized" + print(str(err)) # will print something like "option -a not recognized" usage() sys.exit(2) timeout = None @@ -736,16 +777,19 @@ def main(): # Close connection on exit (to test cleanup paths) old_exitfunc = getattr(sys, 'exitfunc', None) + def exit(): print("Closing " + vc.getURI()) if run: vc.close() - if (old_exitfunc): old_exitfunc() + if (old_exitfunc): + old_exitfunc() + sys.exitfunc = exit vc.registerCloseCallback(myConnectionCloseCallback, None) - #Add 2 lifecycle callbacks to prove this works with more than just one + # Add 2 lifecycle callbacks to prove this works with more than just one vc.domainEventRegister(myDomainEventCallback, 1) domcallbacks = [ vc.domainEventRegisterAny(None, libvirt.VIR_DOMAIN_EVENT_ID_LIFECYCLE, myDomainEventCallback, 2), @@ -829,5 +873,6 @@ def main(): # Allow delayed event loop cleanup to run, just for sake of testing time.sleep(2) + if __name__ == "__main__": main() -- 2.11.0

On 09/21/2018 03:34 PM, Philipp Hahn wrote:
Am 21.09.18 um 13:14 schrieb Michal Privoznik:
On 09/20/2018 08:10 AM, Philipp Hahn wrote:
event-test.py is bad at handling newer livecycle events. Attached are two patches to improve that.
Philipp Hahn (2): event-test.py: Sync list of domain lifecycle events event-test.py: Future proof lifecycle event handling
examples/event-test.py | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-)
ACK to the first patch, no doubt about that. However, the second one - I'm torn. One one hand it makes the code more future proof, on the other - seeing a traceback (which is easy to spot) might inspire users to post patches (because we are obviously not doing a good job in keeping event-test.py in sync with libvirt). What do you think?
Yes, I can understand it, but each time I use event-test.py (spaced months apart) I get the next crash. So I really would like it to be more future poof. If the number is annoying enough, any interested user is free so send a patch to improve it.
As the pattern of printing some details is quiet common in that script, I did a v2 and added a somehow hacky class 'Description' to simplify writing those descriptive texts. Please have a look at the following series as an alternative.
I also found some other issues I fixed along the way.
Philipp Hahn (22): event-test.py: Handle closed connection event-test.py: Remove extra parenthesis event-test.py: Remove dead assignment event-test.py: Add missing globale statement event-test.py: Use __file__ event-test.py: Merge livecycle callbacks event-test.py: Simplify event ID lists event-test.py: Add class for event descriptions event-test.py: Convert LIVECYCLE events event-test.py: Convert BLOCKJOB events event-test.py: Convert WATCHDOG events event-test.py: Convert ERROR events event-test.py: Convert AGENT events event-test.py: Convert GRAPHICS events event-test.py: Convert DISK events event-test.py: Convert TRAY events event-test.py: Convert NETWORK events event-test.py: Convert STORAGE events event-test.py: Convert DEVICE events event-test.py: Convert SECRET events event-test.py: Convert CONNECTION events event-test.py: Fix blanks
examples/event-test.py | 446 +++++++++++++++++++++++++++++-------------------- 1 file changed, 262 insertions(+), 184 deletions(-)
Yup, this looks very good. So I'll merge 1/2 from the original v1 and all 22 patches from this v2. ACK Thanks, Michal
participants (3)
-
Michal Privoznik
-
Peter Krempa
-
Philipp Hahn