Temporary version hack
[libvirt-python/ericb.git] / examples / event-test.py
blob4458e224dfe21db5ca47f24f4cd37dce97ee0b15
1 #!/usr/bin/env python
5 ##############################################################################
6 # Start off by implementing a general purpose event loop for anyone's use
7 ##############################################################################
9 import sys
10 import getopt
11 import os
12 import libvirt
13 import select
14 import errno
15 import time
16 import threading
18 # This example can use three different event loop impls. It defaults
19 # to a portable pure-python impl based on poll that is implemented
20 # in this file.
22 # When Python >= 3.4, it can optionally use an impl based on the
23 # new asyncio module.
25 # Finally, it can also use the libvirt native event loop impl
27 # This setting thus allows 'poll', 'native' or 'asyncio' as valid
28 # choices
30 event_impl = "poll"
32 do_debug = False
35 def debug(msg):
36 global do_debug
37 if do_debug:
38 print(msg)
42 # This general purpose event loop will support waiting for file handle
43 # I/O and errors events, as well as scheduling repeatable timers with
44 # a fixed interval.
46 # It is a pure python implementation based around the poll() API
48 class virEventLoopPoll:
49 # This class contains the data we need to track for a
50 # single file handle
51 class virEventLoopPollHandle:
52 def __init__(self, handle, fd, events, cb, opaque):
53 self.handle = handle
54 self.fd = fd
55 self.events = events
56 self.cb = cb
57 self.opaque = opaque
59 def get_id(self):
60 return self.handle
62 def get_fd(self):
63 return self.fd
65 def get_events(self):
66 return self.events
68 def set_events(self, events):
69 self.events = events
71 def dispatch(self, events):
72 self.cb(self.handle,
73 self.fd,
74 events,
75 self.opaque)
77 # This class contains the data we need to track for a
78 # single periodic timer
79 class virEventLoopPollTimer:
80 def __init__(self, timer, interval, cb, opaque):
81 self.timer = timer
82 self.interval = interval
83 self.cb = cb
84 self.opaque = opaque
85 self.lastfired = 0
87 def get_id(self):
88 return self.timer
90 def get_interval(self):
91 return self.interval
93 def set_interval(self, interval):
94 self.interval = interval
96 def get_last_fired(self):
97 return self.lastfired
99 def set_last_fired(self, now):
100 self.lastfired = now
102 def dispatch(self):
103 self.cb(self.timer,
104 self.opaque)
106 def __init__(self):
107 self.poll = select.poll()
108 self.pipetrick = os.pipe()
109 self.pendingWakeup = False
110 self.runningPoll = False
111 self.nextHandleID = 1
112 self.nextTimerID = 1
113 self.handles = []
114 self.timers = []
115 self.cleanup = []
116 self.quit = False
118 # The event loop can be used from multiple threads at once.
119 # Specifically while the main thread is sleeping in poll()
120 # waiting for events to occur, another thread may come along
121 # and add/update/remove a file handle, or timer. When this
122 # happens we need to interrupt the poll() sleep in the other
123 # thread, so that it'll see the file handle / timer changes.
125 # Using OS level signals for this is very unreliable and
126 # hard to implement correctly. Thus we use the real classic
127 # "self pipe" trick. A anonymous pipe, with one end registered
128 # with the event loop for input events. When we need to force
129 # the main thread out of a poll() sleep, we simple write a
130 # single byte of data to the other end of the pipe.
131 debug("Self pipe watch %d write %d" % (self.pipetrick[0], self.pipetrick[1]))
132 self.poll.register(self.pipetrick[0], select.POLLIN)
134 # Calculate when the next timeout is due to occur, returning
135 # the absolute timestamp for the next timeout, or 0 if there is
136 # no timeout due
137 def next_timeout(self):
138 next = 0
139 for t in self.timers:
140 last = t.get_last_fired()
141 interval = t.get_interval()
142 if interval < 0:
143 continue
144 if next == 0 or (last + interval) < next:
145 next = last + interval
147 return next
149 # Lookup a virEventLoopPollHandle object based on file descriptor
150 def get_handle_by_fd(self, fd):
151 for h in self.handles:
152 if h.get_fd() == fd:
153 return h
154 return None
156 # Lookup a virEventLoopPollHandle object based on its event loop ID
157 def get_handle_by_id(self, handleID):
158 for h in self.handles:
159 if h.get_id() == handleID:
160 return h
161 return None
163 # This is the heart of the event loop, performing one single
164 # iteration. It asks when the next timeout is due, and then
165 # calculates the maximum amount of time it is able to sleep
166 # for in poll() pending file handle events.
168 # It then goes into the poll() sleep.
170 # When poll() returns, there will zero or more file handle
171 # events which need to be dispatched to registered callbacks
172 # It may also be time to fire some periodic timers.
174 # Due to the coarse granularity of scheduler timeslices, if
175 # we ask for a sleep of 500ms in order to satisfy a timer, we
176 # may return up to 1 scheduler timeslice early. So even though
177 # our sleep timeout was reached, the registered timer may not
178 # technically be at its expiry point. This leads to us going
179 # back around the loop with a crazy 5ms sleep. So when checking
180 # if timeouts are due, we allow a margin of 20ms, to avoid
181 # these pointless repeated tiny sleeps.
182 def run_once(self):
183 sleep = -1
184 self.runningPoll = True
186 for opaque in self.cleanup:
187 libvirt.virEventInvokeFreeCallback(opaque)
188 self.cleanup = []
190 try:
191 next = self.next_timeout()
192 debug("Next timeout due at %d" % next)
193 if next > 0:
194 now = int(time.time() * 1000)
195 if now >= next:
196 sleep = 0
197 else:
198 sleep = (next - now) / 1000.0
200 debug("Poll with a sleep of %d" % sleep)
201 events = self.poll.poll(sleep)
203 # Dispatch any file handle events that occurred
204 for (fd, revents) in events:
205 # See if the events was from the self-pipe
206 # telling us to wakup. if so, then discard
207 # the data just continue
208 if fd == self.pipetrick[0]:
209 self.pendingWakeup = False
210 os.read(fd, 1)
211 continue
213 h = self.get_handle_by_fd(fd)
214 if h:
215 debug("Dispatch fd %d handle %d events %d" % (fd, h.get_id(), revents))
216 h.dispatch(self.events_from_poll(revents))
218 now = int(time.time() * 1000)
219 for t in self.timers:
220 interval = t.get_interval()
221 if interval < 0:
222 continue
224 want = t.get_last_fired() + interval
225 # Deduct 20ms, since scheduler timeslice
226 # means we could be ever so slightly early
227 if now >= want - 20:
228 debug("Dispatch timer %d now %s want %s" % (t.get_id(), str(now), str(want)))
229 t.set_last_fired(now)
230 t.dispatch()
232 except (os.error, select.error) as e:
233 if e.args[0] != errno.EINTR:
234 raise
235 finally:
236 self.runningPoll = False
238 # Actually run the event loop forever
239 def run_loop(self):
240 self.quit = False
241 while not self.quit:
242 self.run_once()
244 def interrupt(self):
245 if self.runningPoll and not self.pendingWakeup:
246 self.pendingWakeup = True
247 os.write(self.pipetrick[1], 'c'.encode("UTF-8"))
249 # Registers a new file handle 'fd', monitoring for 'events' (libvirt
250 # event constants), firing the callback cb() when an event occurs.
251 # Returns a unique integer identier for this handle, that should be
252 # used to later update/remove it
253 def add_handle(self, fd, events, cb, opaque):
254 handleID = self.nextHandleID + 1
255 self.nextHandleID = self.nextHandleID + 1
257 h = self.virEventLoopPollHandle(handleID, fd, events, cb, opaque)
258 self.handles.append(h)
260 self.poll.register(fd, self.events_to_poll(events))
261 self.interrupt()
263 debug("Add handle %d fd %d events %d" % (handleID, fd, events))
265 return handleID
267 # Registers a new timer with periodic expiry at 'interval' ms,
268 # firing cb() each time the timer expires. If 'interval' is -1,
269 # then the timer is registered, but not enabled
270 # Returns a unique integer identier for this handle, that should be
271 # used to later update/remove it
272 def add_timer(self, interval, cb, opaque):
273 timerID = self.nextTimerID + 1
274 self.nextTimerID = self.nextTimerID + 1
276 h = self.virEventLoopPollTimer(timerID, interval, cb, opaque)
277 self.timers.append(h)
278 self.interrupt()
280 debug("Add timer %d interval %d" % (timerID, interval))
282 return timerID
284 # Change the set of events to be monitored on the file handle
285 def update_handle(self, handleID, events):
286 h = self.get_handle_by_id(handleID)
287 if h:
288 h.set_events(events)
289 self.poll.unregister(h.get_fd())
290 self.poll.register(h.get_fd(), self.events_to_poll(events))
291 self.interrupt()
293 debug("Update handle %d fd %d events %d" % (handleID, h.get_fd(), events))
295 # Change the periodic frequency of the timer
296 def update_timer(self, timerID, interval):
297 for h in self.timers:
298 if h.get_id() == timerID:
299 h.set_interval(interval)
300 self.interrupt()
302 debug("Update timer %d interval %d" % (timerID, interval))
303 break
305 # Stop monitoring for events on the file handle
306 def remove_handle(self, handleID):
307 handles = []
308 for h in self.handles:
309 if h.get_id() == handleID:
310 debug("Remove handle %d fd %d" % (handleID, h.get_fd()))
311 self.poll.unregister(h.get_fd())
312 self.cleanup.append(h.opaque)
313 else:
314 handles.append(h)
315 self.handles = handles
316 self.interrupt()
318 # Stop firing the periodic timer
319 def remove_timer(self, timerID):
320 timers = []
321 for h in self.timers:
322 if h.get_id() != timerID:
323 timers.append(h)
324 else:
325 debug("Remove timer %d" % timerID)
326 self.cleanup.append(h.opaque)
327 self.timers = timers
328 self.interrupt()
330 # Convert from libvirt event constants, to poll() events constants
331 def events_to_poll(self, events):
332 ret = 0
333 if events & libvirt.VIR_EVENT_HANDLE_READABLE:
334 ret |= select.POLLIN
335 if events & libvirt.VIR_EVENT_HANDLE_WRITABLE:
336 ret |= select.POLLOUT
337 if events & libvirt.VIR_EVENT_HANDLE_ERROR:
338 ret |= select.POLLERR
339 if events & libvirt.VIR_EVENT_HANDLE_HANGUP:
340 ret |= select.POLLHUP
341 return ret
343 # Convert from poll() event constants, to libvirt events constants
344 def events_from_poll(self, events):
345 ret = 0
346 if events & select.POLLIN:
347 ret |= libvirt.VIR_EVENT_HANDLE_READABLE
348 if events & select.POLLOUT:
349 ret |= libvirt.VIR_EVENT_HANDLE_WRITABLE
350 if events & select.POLLNVAL:
351 ret |= libvirt.VIR_EVENT_HANDLE_ERROR
352 if events & select.POLLERR:
353 ret |= libvirt.VIR_EVENT_HANDLE_ERROR
354 if events & select.POLLHUP:
355 ret |= libvirt.VIR_EVENT_HANDLE_HANGUP
356 return ret
359 ###########################################################################
360 # Now glue an instance of the general event loop into libvirt's event loop
361 ###########################################################################
363 # This single global instance of the event loop wil be used for
364 # monitoring libvirt events
365 eventLoop = virEventLoopPoll()
367 # This keeps track of what thread is running the event loop,
368 # (if it is run in a background thread)
369 eventLoopThread = None
372 # These next set of 6 methods are the glue between the official
373 # libvirt events API, and our particular impl of the event loop
375 # There is no reason why the 'virEventLoopPoll' has to be used.
376 # An application could easily may these 6 glue methods hook into
377 # another event loop such as GLib's, or something like the python
378 # Twisted event framework.
380 def virEventAddHandleImpl(fd, events, cb, opaque):
381 global eventLoop
382 return eventLoop.add_handle(fd, events, cb, opaque)
385 def virEventUpdateHandleImpl(handleID, events):
386 global eventLoop
387 return eventLoop.update_handle(handleID, events)
390 def virEventRemoveHandleImpl(handleID):
391 global eventLoop
392 return eventLoop.remove_handle(handleID)
395 def virEventAddTimerImpl(interval, cb, opaque):
396 global eventLoop
397 return eventLoop.add_timer(interval, cb, opaque)
400 def virEventUpdateTimerImpl(timerID, interval):
401 global eventLoop
402 return eventLoop.update_timer(timerID, interval)
405 def virEventRemoveTimerImpl(timerID):
406 global eventLoop
407 return eventLoop.remove_timer(timerID)
410 # This tells libvirt what event loop implementation it
411 # should use
412 def virEventLoopPollRegister():
413 libvirt.virEventRegisterImpl(virEventAddHandleImpl,
414 virEventUpdateHandleImpl,
415 virEventRemoveHandleImpl,
416 virEventAddTimerImpl,
417 virEventUpdateTimerImpl,
418 virEventRemoveTimerImpl)
421 # Directly run the event loop in the current thread
422 def virEventLoopPollRun():
423 global eventLoop
424 eventLoop.run_loop()
427 def virEventLoopAIORun(loop):
428 import asyncio
429 asyncio.set_event_loop(loop)
430 loop.run_forever()
433 def virEventLoopNativeRun():
434 while True:
435 libvirt.virEventRunDefaultImpl()
438 # Spawn a background thread to run the event loop
439 def virEventLoopPollStart():
440 global eventLoopThread
441 virEventLoopPollRegister()
442 eventLoopThread = threading.Thread(target=virEventLoopPollRun, name="libvirtEventLoop")
443 eventLoopThread.setDaemon(True)
444 eventLoopThread.start()
447 def virEventLoopAIOStart():
448 global eventLoopThread
449 import libvirtaio
450 import asyncio
451 loop = asyncio.new_event_loop()
452 libvirtaio.virEventRegisterAsyncIOImpl(loop=loop)
453 eventLoopThread = threading.Thread(target=virEventLoopAIORun, args=(loop,), name="libvirtEventLoop")
454 eventLoopThread.setDaemon(True)
455 eventLoopThread.start()
458 def virEventLoopNativeStart():
459 global eventLoopThread
460 libvirt.virEventRegisterDefaultImpl()
461 eventLoopThread = threading.Thread(target=virEventLoopNativeRun, name="libvirtEventLoop")
462 eventLoopThread.setDaemon(True)
463 eventLoopThread.start()
466 ##########################################################################
467 # Everything that now follows is a simple demo of domain lifecycle events
468 ##########################################################################
469 class Description(object):
470 __slots__ = ('desc', 'args')
472 def __init__(self, *args, **kwargs):
473 self.desc = kwargs.get('desc')
474 self.args = args
476 def __str__(self): # type: () -> str
477 return self.desc
479 def __getitem__(self, item): # type: (int) -> str
480 try:
481 data = self.args[item]
482 except IndexError:
483 return self.__class__(desc=str(item))
485 if isinstance(data, str):
486 return self.__class__(desc=data)
487 elif isinstance(data, (list, tuple)):
488 desc, args = data
489 return self.__class__(*args, desc=desc)
491 raise TypeError(args)
494 DOM_EVENTS = Description(
495 ("Defined", ("Added", "Updated", "Renamed", "Snapshot")),
496 ("Undefined", ("Removed", "Renamed")),
497 ("Started", ("Booted", "Migrated", "Restored", "Snapshot", "Wakeup")),
498 ("Suspended", ("Paused", "Migrated", "IOError", "Watchdog", "Restored", "Snapshot", "API error", "Postcopy", "Postcopy failed")),
499 ("Resumed", ("Unpaused", "Migrated", "Snapshot", "Postcopy")),
500 ("Stopped", ("Shutdown", "Destroyed", "Crashed", "Migrated", "Saved", "Failed", "Snapshot", "Daemon")),
501 ("Shutdown", ("Finished", "On guest request", "On host request")),
502 ("PMSuspended", ("Memory", "Disk")),
503 ("Crashed", ("Panicked",)),
505 BLOCK_JOB_TYPES = Description("unknown", "Pull", "Copy", "Commit", "ActiveCommit")
506 BLOCK_JOB_STATUS = Description("Completed", "Failed", "Canceled", "Ready")
507 WATCHDOG_ACTIONS = Description("none", "Pause", "Reset", "Poweroff", "Shutdown", "Debug", "Inject NMI")
508 ERROR_EVENTS = Description("None", "Pause", "Report")
509 AGENT_STATES = Description("unknown", "connected", "disconnected")
510 AGENT_REASONS = Description("unknown", "domain started", "channel event")
511 GRAPHICS_PHASES = Description("Connect", "Initialize", "Disconnect")
512 DISK_EVENTS = Description("Change missing on start", "Drop missing on start")
513 TRAY_EVENTS = Description("Opened", "Closed")
516 def myDomainEventCallback(conn, dom, event, detail, opaque):
517 print("myDomainEventCallback%s EVENT: Domain %s(%s) %s %s" % (
518 opaque, dom.name(), dom.ID(), DOM_EVENTS[event], DOM_EVENTS[event][detail]))
521 def myDomainEventRebootCallback(conn, dom, opaque):
522 print("myDomainEventRebootCallback: Domain %s(%s)" % (
523 dom.name(), dom.ID()))
526 def myDomainEventRTCChangeCallback(conn, dom, utcoffset, opaque):
527 print("myDomainEventRTCChangeCallback: Domain %s(%s) %d" % (
528 dom.name(), dom.ID(), utcoffset))
531 def myDomainEventWatchdogCallback(conn, dom, action, opaque):
532 print("myDomainEventWatchdogCallback: Domain %s(%s) %s" % (
533 dom.name(), dom.ID(), WATCHDOG_ACTIONS[action]))
536 def myDomainEventIOErrorCallback(conn, dom, srcpath, devalias, action, opaque):
537 print("myDomainEventIOErrorCallback: Domain %s(%s) %s %s %s" % (
538 dom.name(), dom.ID(), srcpath, devalias, ERROR_EVENTS[action]))
541 def myDomainEventIOErrorReasonCallback(conn, dom, srcpath, devalias, action, reason, opaque):
542 print("myDomainEventIOErrorReasonCallback: Domain %s(%s) %s %s %s %s" % (
543 dom.name(), dom.ID(), srcpath, devalias, ERROR_EVENTS[action], reason))
546 def myDomainEventGraphicsCallback(conn, dom, phase, localAddr, remoteAddr, authScheme, subject, opaque):
547 print("myDomainEventGraphicsCallback: Domain %s(%s) %s %s" % (
548 dom.name(), dom.ID(), GRAPHICS_PHASES[phase], authScheme))
551 def myDomainEventControlErrorCallback(conn, dom, opaque):
552 print("myDomainEventControlErrorCallback: Domain %s(%s)" % (
553 dom.name(), dom.ID()))
556 def myDomainEventBlockJobCallback(conn, dom, disk, type, status, opaque):
557 print("myDomainEventBlockJobCallback: Domain %s(%s) %s on disk %s %s" % (
558 dom.name(), dom.ID(), BLOCK_JOB_TYPES[type], disk, BLOCK_JOB_STATUS[status]))
561 def myDomainEventDiskChangeCallback(conn, dom, oldSrcPath, newSrcPath, devAlias, reason, opaque):
562 print("myDomainEventDiskChangeCallback: Domain %s(%s) disk change oldSrcPath: %s newSrcPath: %s devAlias: %s reason: %s" % (
563 dom.name(), dom.ID(), oldSrcPath, newSrcPath, devAlias, DISK_EVENTS[reason]))
566 def myDomainEventTrayChangeCallback(conn, dom, devAlias, reason, opaque):
567 print("myDomainEventTrayChangeCallback: Domain %s(%s) tray change devAlias: %s reason: %s" % (
568 dom.name(), dom.ID(), devAlias, TRAY_EVENTS[reason]))
571 def myDomainEventPMWakeupCallback(conn, dom, reason, opaque):
572 print("myDomainEventPMWakeupCallback: Domain %s(%s) system pmwakeup" % (
573 dom.name(), dom.ID()))
576 def myDomainEventPMSuspendCallback(conn, dom, reason, opaque):
577 print("myDomainEventPMSuspendCallback: Domain %s(%s) system pmsuspend" % (
578 dom.name(), dom.ID()))
581 def myDomainEventBalloonChangeCallback(conn, dom, actual, opaque):
582 print("myDomainEventBalloonChangeCallback: Domain %s(%s) %d" % (
583 dom.name(), dom.ID(), actual))
586 def myDomainEventPMSuspendDiskCallback(conn, dom, reason, opaque):
587 print("myDomainEventPMSuspendDiskCallback: Domain %s(%s) system pmsuspend_disk" % (
588 dom.name(), dom.ID()))
591 def myDomainEventDeviceRemovedCallback(conn, dom, dev, opaque):
592 print("myDomainEventDeviceRemovedCallback: Domain %s(%s) device removed: %s" % (
593 dom.name(), dom.ID(), dev))
596 def myDomainEventBlockJob2Callback(conn, dom, disk, type, status, opaque):
597 print("myDomainEventBlockJob2Callback: Domain %s(%s) %s on disk %s %s" % (
598 dom.name(), dom.ID(), BLOCK_JOB_TYPES[type], disk, BLOCK_JOB_STATUS[status]))
601 def myDomainEventTunableCallback(conn, dom, params, opaque):
602 print("myDomainEventTunableCallback: Domain %s(%s) %s" % (
603 dom.name(), dom.ID(), params))
606 def myDomainEventAgentLifecycleCallback(conn, dom, state, reason, opaque):
607 print("myDomainEventAgentLifecycleCallback: Domain %s(%s) %s %s" % (
608 dom.name(), dom.ID(), AGENT_STATES[state], AGENT_REASONS[reason]))
611 def myDomainEventDeviceAddedCallback(conn, dom, dev, opaque):
612 print("myDomainEventDeviceAddedCallback: Domain %s(%s) device added: %s" % (
613 dom.name(), dom.ID(), dev))
616 def myDomainEventMigrationIteration(conn, dom, iteration, opaque):
617 print("myDomainEventMigrationIteration: Domain %s(%s) started migration iteration %d" % (
618 dom.name(), dom.ID(), iteration))
621 def myDomainEventJobCompletedCallback(conn, dom, params, opaque):
622 print("myDomainEventJobCompletedCallback: Domain %s(%s) %s" % (
623 dom.name(), dom.ID(), params))
626 def myDomainEventDeviceRemovalFailedCallback(conn, dom, dev, opaque):
627 print("myDomainEventDeviceRemovalFailedCallback: Domain %s(%s) failed to remove device: %s" % (
628 dom.name(), dom.ID(), dev))
631 def myDomainEventMetadataChangeCallback(conn, dom, mtype, nsuri, opaque):
632 print("myDomainEventMetadataChangeCallback: Domain %s(%s) changed metadata mtype=%d nsuri=%s" % (
633 dom.name(), dom.ID(), mtype, nsuri))
636 def myDomainEventBlockThresholdCallback(conn, dom, dev, path, threshold, excess, opaque):
637 print("myDomainEventBlockThresholdCallback: Domain %s(%s) block device %s(%s) threshold %d exceeded by %d" % (
638 dom.name(), dom.ID(), dev, path, threshold, excess))
641 ##########################################################################
642 # Network events
643 ##########################################################################
644 NET_EVENTS = Description(
645 ("Defined", ("Added",)),
646 ("Undefined", ("Removed",)),
647 ("Started", ("Started",)),
648 ("Stopped", ("Stopped",)),
652 def myNetworkEventLifecycleCallback(conn, net, event, detail, opaque):
653 print("myNetworkEventLifecycleCallback: Network %s %s %s" % (
654 net.name(), NET_EVENTS[event], NET_EVENTS[event][detail]))
657 ##########################################################################
658 # Storage pool events
659 ##########################################################################
660 STORAGE_EVENTS = Description(
661 ("Defined", ()),
662 ("Undefined", ()),
663 ("Started", ()),
664 ("Stopped", ()),
665 ("Created", ()),
666 ("Deleted", ()),
670 def myStoragePoolEventLifecycleCallback(conn, pool, event, detail, opaque):
671 print("myStoragePoolEventLifecycleCallback: Storage pool %s %s %s" % (
672 pool.name(), STORAGE_EVENTS[event], STORAGE_EVENTS[event][detail]))
675 def myStoragePoolEventRefreshCallback(conn, pool, opaque):
676 print("myStoragePoolEventRefreshCallback: Storage pool %s" % pool.name())
679 ##########################################################################
680 # Node device events
681 ##########################################################################
682 DEVICE_EVENTS = Description(
683 ("Created", ()),
684 ("Deleted", ()),
688 def myNodeDeviceEventLifecycleCallback(conn, dev, event, detail, opaque):
689 print("myNodeDeviceEventLifecycleCallback: Node device %s %s %s" % (
690 dev.name(), DEVICE_EVENTS[event], DEVICE_EVENTS[event][detail]))
693 def myNodeDeviceEventUpdateCallback(conn, dev, opaque):
694 print("myNodeDeviceEventUpdateCallback: Node device %s" % dev.name())
697 ##########################################################################
698 # Secret events
699 ##########################################################################
700 SECRET_EVENTS = Description(
701 ("Defined", ()),
702 ("Undefined", ()),
706 def mySecretEventLifecycleCallback(conn, secret, event, detail, opaque):
707 print("mySecretEventLifecycleCallback: Secret %s %s %s" % (
708 secret.UUIDString(), SECRET_EVENTS[event], SECRET_EVENTS[event][detail]))
711 def mySecretEventValueChanged(conn, secret, opaque):
712 print("mySecretEventValueChanged: Secret %s" % secret.UUIDString())
715 ##########################################################################
716 # Set up and run the program
717 ##########################################################################
719 run = True
720 CONNECTION_EVENTS = Description("Error", "End-of-file", "Keepalive", "Client")
723 def myConnectionCloseCallback(conn, reason, opaque):
724 print("myConnectionCloseCallback: %s: %s" % (
725 conn.getURI(), CONNECTION_EVENTS[reason]))
726 global run
727 run = False
730 def usage():
731 print("usage: %s [-hdl] [uri]" % (os.path.basename(__file__),))
732 print(" uri will default to qemu:///system")
733 print(" --help, -h Print this help message")
734 print(" --debug, -d Print debug output")
735 print(" --loop=TYPE, -l Choose event-loop-implementation (native, poll, asyncio)")
736 print(" --timeout=SECS Quit after SECS seconds running")
739 def main():
740 try:
741 opts, args = getopt.getopt(sys.argv[1:], "hdl:", ["help", "debug", "loop=", "timeout="])
742 except getopt.GetoptError as err:
743 # print help information and exit:
744 print(str(err)) # will print something like "option -a not recognized"
745 usage()
746 sys.exit(2)
747 timeout = None
748 for o, a in opts:
749 if o in ("-h", "--help"):
750 usage()
751 sys.exit()
752 if o in ("-d", "--debug"):
753 global do_debug
754 do_debug = True
755 if o in ("-l", "--loop"):
756 global event_impl
757 event_impl = a
758 if o in ("--timeout"):
759 timeout = int(a)
761 if len(args) >= 1:
762 uri = args[0]
763 else:
764 uri = "qemu:///system"
766 print("Using uri '%s' and event loop '%s'" % (uri, event_impl))
768 # Run a background thread with the event loop
769 if event_impl == "poll":
770 virEventLoopPollStart()
771 elif event_impl == "asyncio":
772 virEventLoopAIOStart()
773 else:
774 virEventLoopNativeStart()
776 vc = libvirt.openReadOnly(uri)
778 # Close connection on exit (to test cleanup paths)
779 old_exitfunc = getattr(sys, 'exitfunc', None)
781 def exit():
782 print("Closing " + vc.getURI())
783 if run:
784 vc.close()
785 if (old_exitfunc):
786 old_exitfunc()
788 sys.exitfunc = exit
790 vc.registerCloseCallback(myConnectionCloseCallback, None)
792 # Add 2 lifecycle callbacks to prove this works with more than just one
793 vc.domainEventRegister(myDomainEventCallback, 1)
794 domcallbacks = [
795 vc.domainEventRegisterAny(None, libvirt.VIR_DOMAIN_EVENT_ID_LIFECYCLE, myDomainEventCallback, 2),
796 vc.domainEventRegisterAny(None, libvirt.VIR_DOMAIN_EVENT_ID_REBOOT, myDomainEventRebootCallback, None),
797 vc.domainEventRegisterAny(None, libvirt.VIR_DOMAIN_EVENT_ID_RTC_CHANGE, myDomainEventRTCChangeCallback, None),
798 vc.domainEventRegisterAny(None, libvirt.VIR_DOMAIN_EVENT_ID_WATCHDOG, myDomainEventWatchdogCallback, None),
799 vc.domainEventRegisterAny(None, libvirt.VIR_DOMAIN_EVENT_ID_IO_ERROR, myDomainEventIOErrorCallback, None),
800 vc.domainEventRegisterAny(None, libvirt.VIR_DOMAIN_EVENT_ID_GRAPHICS, myDomainEventGraphicsCallback, None),
801 vc.domainEventRegisterAny(None, libvirt.VIR_DOMAIN_EVENT_ID_IO_ERROR_REASON, myDomainEventIOErrorReasonCallback, None),
802 vc.domainEventRegisterAny(None, libvirt.VIR_DOMAIN_EVENT_ID_CONTROL_ERROR, myDomainEventControlErrorCallback, None),
803 vc.domainEventRegisterAny(None, libvirt.VIR_DOMAIN_EVENT_ID_BLOCK_JOB, myDomainEventBlockJobCallback, None),
804 vc.domainEventRegisterAny(None, libvirt.VIR_DOMAIN_EVENT_ID_DISK_CHANGE, myDomainEventDiskChangeCallback, None),
805 vc.domainEventRegisterAny(None, libvirt.VIR_DOMAIN_EVENT_ID_TRAY_CHANGE, myDomainEventTrayChangeCallback, None),
806 vc.domainEventRegisterAny(None, libvirt.VIR_DOMAIN_EVENT_ID_PMWAKEUP, myDomainEventPMWakeupCallback, None),
807 vc.domainEventRegisterAny(None, libvirt.VIR_DOMAIN_EVENT_ID_PMSUSPEND, myDomainEventPMSuspendCallback, None),
808 vc.domainEventRegisterAny(None, libvirt.VIR_DOMAIN_EVENT_ID_BALLOON_CHANGE, myDomainEventBalloonChangeCallback, None),
809 vc.domainEventRegisterAny(None, libvirt.VIR_DOMAIN_EVENT_ID_PMSUSPEND_DISK, myDomainEventPMSuspendDiskCallback, None),
810 vc.domainEventRegisterAny(None, libvirt.VIR_DOMAIN_EVENT_ID_DEVICE_REMOVED, myDomainEventDeviceRemovedCallback, None),
811 vc.domainEventRegisterAny(None, libvirt.VIR_DOMAIN_EVENT_ID_BLOCK_JOB_2, myDomainEventBlockJob2Callback, None),
812 vc.domainEventRegisterAny(None, libvirt.VIR_DOMAIN_EVENT_ID_TUNABLE, myDomainEventTunableCallback, None),
813 vc.domainEventRegisterAny(None, libvirt.VIR_DOMAIN_EVENT_ID_AGENT_LIFECYCLE, myDomainEventAgentLifecycleCallback, None),
814 vc.domainEventRegisterAny(None, libvirt.VIR_DOMAIN_EVENT_ID_DEVICE_ADDED, myDomainEventDeviceAddedCallback, None),
815 vc.domainEventRegisterAny(None, libvirt.VIR_DOMAIN_EVENT_ID_MIGRATION_ITERATION, myDomainEventMigrationIteration, None),
816 vc.domainEventRegisterAny(None, libvirt.VIR_DOMAIN_EVENT_ID_JOB_COMPLETED, myDomainEventJobCompletedCallback, None),
817 vc.domainEventRegisterAny(None, libvirt.VIR_DOMAIN_EVENT_ID_DEVICE_REMOVAL_FAILED, myDomainEventDeviceRemovalFailedCallback, None),
818 vc.domainEventRegisterAny(None, libvirt.VIR_DOMAIN_EVENT_ID_METADATA_CHANGE, myDomainEventMetadataChangeCallback, None),
819 vc.domainEventRegisterAny(None, libvirt.VIR_DOMAIN_EVENT_ID_BLOCK_THRESHOLD, myDomainEventBlockThresholdCallback, None),
822 netcallbacks = [
823 vc.networkEventRegisterAny(None, libvirt.VIR_NETWORK_EVENT_ID_LIFECYCLE, myNetworkEventLifecycleCallback, None),
826 poolcallbacks = [
827 vc.storagePoolEventRegisterAny(None, libvirt.VIR_STORAGE_POOL_EVENT_ID_LIFECYCLE, myStoragePoolEventLifecycleCallback, None),
828 vc.storagePoolEventRegisterAny(None, libvirt.VIR_STORAGE_POOL_EVENT_ID_REFRESH, myStoragePoolEventRefreshCallback, None),
831 devcallbacks = [
832 vc.nodeDeviceEventRegisterAny(None, libvirt.VIR_NODE_DEVICE_EVENT_ID_LIFECYCLE, myNodeDeviceEventLifecycleCallback, None),
833 vc.nodeDeviceEventRegisterAny(None, libvirt.VIR_NODE_DEVICE_EVENT_ID_UPDATE, myNodeDeviceEventUpdateCallback, None),
836 seccallbacks = [
837 vc.secretEventRegisterAny(None, libvirt.VIR_SECRET_EVENT_ID_LIFECYCLE, mySecretEventLifecycleCallback, None),
838 vc.secretEventRegisterAny(None, libvirt.VIR_SECRET_EVENT_ID_VALUE_CHANGED, mySecretEventValueChanged, None),
841 vc.setKeepAlive(5, 3)
843 # The rest of your app would go here normally, but for sake
844 # of demo we'll just go to sleep. The other option is to
845 # run the event loop in your main thread if your app is
846 # totally event based.
847 count = 0
848 while run and (timeout is None or count < timeout):
849 count = count + 1
850 time.sleep(1)
852 # If the connection was closed, we cannot unregister anything.
853 # Just abort now.
854 if not run:
855 return
857 vc.domainEventDeregister(myDomainEventCallback)
859 for id in seccallbacks:
860 vc.secretEventDeregisterAny(id)
861 for id in devcallbacks:
862 vc.nodeDeviceEventDeregisterAny(id)
863 for id in poolcallbacks:
864 vc.storagePoolEventDeregisterAny(id)
865 for id in netcallbacks:
866 vc.networkEventDeregisterAny(id)
867 for id in domcallbacks:
868 vc.domainEventDeregisterAny(id)
870 vc.unregisterCloseCallback()
871 vc.close()
873 # Allow delayed event loop cleanup to run, just for sake of testing
874 time.sleep(2)
877 if __name__ == "__main__":
878 main()