5 ##############################################################################
6 # Start off by implementing a general purpose event loop for anyone's use
7 ##############################################################################
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
22 # When Python >= 3.4, it can optionally use an impl based on the
25 # Finally, it can also use the libvirt native event loop impl
27 # This setting thus allows 'poll', 'native' or 'asyncio' as valid
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
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
51 class virEventLoopPollHandle
:
52 def __init__(self
, handle
, fd
, events
, cb
, opaque
):
68 def set_events(self
, events
):
71 def dispatch(self
, events
):
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
):
82 self
.interval
= interval
90 def get_interval(self
):
93 def set_interval(self
, interval
):
94 self
.interval
= interval
96 def get_last_fired(self
):
99 def set_last_fired(self
, now
):
107 self
.poll
= select
.poll()
108 self
.pipetrick
= os
.pipe()
109 self
.pendingWakeup
= False
110 self
.runningPoll
= False
111 self
.nextHandleID
= 1
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
137 def next_timeout(self
):
139 for t
in self
.timers
:
140 last
= t
.get_last_fired()
141 interval
= t
.get_interval()
144 if next
== 0 or (last
+ interval
) < next
:
145 next
= last
+ interval
149 # Lookup a virEventLoopPollHandle object based on file descriptor
150 def get_handle_by_fd(self
, fd
):
151 for h
in self
.handles
:
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
:
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.
184 self
.runningPoll
= True
186 for opaque
in self
.cleanup
:
187 libvirt
.virEventInvokeFreeCallback(opaque
)
191 next
= self
.next_timeout()
192 debug("Next timeout due at %d" % next
)
194 now
= int(time
.time() * 1000)
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
213 h
= self
.get_handle_by_fd(fd
)
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()
224 want
= t
.get_last_fired() + interval
225 # Deduct 20ms, since scheduler timeslice
226 # means we could be ever so slightly early
228 debug("Dispatch timer %d now %s want %s" % (t
.get_id(), str(now
), str(want
)))
229 t
.set_last_fired(now
)
232 except (os
.error
, select
.error
) as e
:
233 if e
.args
[0] != errno
.EINTR
:
236 self
.runningPoll
= False
238 # Actually run the event loop forever
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
))
263 debug("Add handle %d fd %d events %d" % (handleID
, fd
, events
))
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
)
280 debug("Add timer %d interval %d" % (timerID
, interval
))
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
)
289 self
.poll
.unregister(h
.get_fd())
290 self
.poll
.register(h
.get_fd(), self
.events_to_poll(events
))
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
)
302 debug("Update timer %d interval %d" % (timerID
, interval
))
305 # Stop monitoring for events on the file handle
306 def remove_handle(self
, handleID
):
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
)
315 self
.handles
= handles
318 # Stop firing the periodic timer
319 def remove_timer(self
, timerID
):
321 for h
in self
.timers
:
322 if h
.get_id() != timerID
:
325 debug("Remove timer %d" % timerID
)
326 self
.cleanup
.append(h
.opaque
)
330 # Convert from libvirt event constants, to poll() events constants
331 def events_to_poll(self
, events
):
333 if events
& libvirt
.VIR_EVENT_HANDLE_READABLE
:
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
343 # Convert from poll() event constants, to libvirt events constants
344 def events_from_poll(self
, events
):
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
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
):
382 return eventLoop
.add_handle(fd
, events
, cb
, opaque
)
385 def virEventUpdateHandleImpl(handleID
, events
):
387 return eventLoop
.update_handle(handleID
, events
)
390 def virEventRemoveHandleImpl(handleID
):
392 return eventLoop
.remove_handle(handleID
)
395 def virEventAddTimerImpl(interval
, cb
, opaque
):
397 return eventLoop
.add_timer(interval
, cb
, opaque
)
400 def virEventUpdateTimerImpl(timerID
, interval
):
402 return eventLoop
.update_timer(timerID
, interval
)
405 def virEventRemoveTimerImpl(timerID
):
407 return eventLoop
.remove_timer(timerID
)
410 # This tells libvirt what event loop implementation it
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():
427 def virEventLoopAIORun(loop
):
429 asyncio
.set_event_loop(loop
)
433 def virEventLoopNativeRun():
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
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')
476 def __str__(self
): # type: () -> str
479 def __getitem__(self
, item
): # type: (int) -> str
481 data
= self
.args
[item
]
483 return self
.__class
__(desc
=str(item
))
485 if isinstance(data
, str):
486 return self
.__class
__(desc
=data
)
487 elif isinstance(data
, (list, tuple)):
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 ##########################################################################
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(
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 ##########################################################################
681 ##########################################################################
682 DEVICE_EVENTS
= Description(
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 ##########################################################################
699 ##########################################################################
700 SECRET_EVENTS
= Description(
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 ##########################################################################
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
]))
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")
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"
749 if o
in ("-h", "--help"):
752 if o
in ("-d", "--debug"):
755 if o
in ("-l", "--loop"):
758 if o
in ("--timeout"):
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()
774 virEventLoopNativeStart()
776 vc
= libvirt
.openReadOnly(uri
)
778 # Close connection on exit (to test cleanup paths)
779 old_exitfunc
= getattr(sys
, 'exitfunc', None)
782 print("Closing " + vc
.getURI())
790 vc
.registerCloseCallback(myConnectionCloseCallback
, None)
792 # Add 2 lifecycle callbacks to prove this works with more than just one
793 vc
.domainEventRegister(myDomainEventCallback
, 1)
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),
823 vc
.networkEventRegisterAny(None, libvirt
.VIR_NETWORK_EVENT_ID_LIFECYCLE
, myNetworkEventLifecycleCallback
, None),
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),
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),
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.
848 while run
and (timeout
is None or count
< timeout
):
852 # If the connection was closed, we cannot unregister anything.
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()
873 # Allow delayed event loop cleanup to run, just for sake of testing
877 if __name__
== "__main__":