event-test.py 22.0 KB
Newer Older
1
#!/usr/bin/python -u
2 3 4 5 6 7 8 9 10 11
#
#
#
#################################################################################
# Start off by implementing a general purpose event loop for anyones use
#################################################################################

import sys
import getopt
import os
12 13
import libvirt
import select
14 15 16 17
import errno
import time
import threading

18 19 20 21 22 23 24 25 26 27 28
# For the sake of demonstration, this example program includes
# an implementation of a pure python event loop. Most applications
# would be better off just using the default libvirt event loop
# APIs, instead of implementing this in python. The exception is
# where an application wants to integrate with an existing 3rd
# party event loop impl
#
# Change this to 'False' to make the demo use the native
# libvirt event loop impl
use_pure_python_event_loop = True

29 30 31 32 33 34
do_debug = False
def debug(msg):
    global do_debug
    if do_debug:
        print msg

35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68
#
# This general purpose event loop will support waiting for file handle
# I/O and errors events, as well as scheduling repeatable timers with
# a fixed interval.
#
# It is a pure python implementation based around the poll() API
#
class virEventLoopPure:
    # This class contains the data we need to track for a
    # single file handle
    class virEventLoopPureHandle:
        def __init__(self, handle, fd, events, cb, opaque):
            self.handle = handle
            self.fd = fd
            self.events = events
            self.cb = cb
            self.opaque = opaque

        def get_id(self):
            return self.handle

        def get_fd(self):
            return self.fd

        def get_events(self):
            return self.events

        def set_events(self, events):
            self.events = events

        def dispatch(self, events):
            self.cb(self.handle,
                    self.fd,
                    events,
69
                    self.opaque)
70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97

    # This class contains the data we need to track for a
    # single periodic timer
    class virEventLoopPureTimer:
        def __init__(self, timer, interval, cb, opaque):
            self.timer = timer
            self.interval = interval
            self.cb = cb
            self.opaque = opaque
            self.lastfired = 0

        def get_id(self):
            return self.timer

        def get_interval(self):
            return self.interval

        def set_interval(self, interval):
            self.interval = interval

        def get_last_fired(self):
            return self.lastfired

        def set_last_fired(self, now):
            self.lastfired = now

        def dispatch(self):
            self.cb(self.timer,
98
                    self.opaque)
99 100


101
    def __init__(self):
102 103
        self.poll = select.poll()
        self.pipetrick = os.pipe()
104 105
        self.pendingWakeup = False
        self.runningPoll = False
106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124
        self.nextHandleID = 1
        self.nextTimerID = 1
        self.handles = []
        self.timers = []
        self.quit = False

        # The event loop can be used from multiple threads at once.
        # Specifically while the main thread is sleeping in poll()
        # waiting for events to occur, another thread may come along
        # and add/update/remove a file handle, or timer. When this
        # happens we need to interrupt the poll() sleep in the other
        # thread, so that it'll see the file handle / timer changes.
        #
        # Using OS level signals for this is very unreliable and
        # hard to implement correctly. Thus we use the real classic
        # "self pipe" trick. A anonymous pipe, with one end registered
        # 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.
125
        debug("Self pipe watch %d write %d" %(self.pipetrick[0], self.pipetrick[1]))
126 127 128
        self.poll.register(self.pipetrick[0], select.POLLIN)


J
Ján Tomko 已提交
129
    # Calculate when the next timeout is due to occur, returning
130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171
    # the absolute timestamp for the next timeout, or 0 if there is
    # no timeout due
    def next_timeout(self):
        next = 0
        for t in self.timers:
            last = t.get_last_fired()
            interval = t.get_interval()
            if interval < 0:
                continue
            if next == 0 or (last + interval) < next:
                next = last + interval

        return next

    # Lookup a virEventLoopPureHandle object based on file descriptor
    def get_handle_by_fd(self, fd):
        for h in self.handles:
            if h.get_fd() == fd:
                return h
        return None

    # Lookup a virEventLoopPureHandle object based on its event loop ID
    def get_handle_by_id(self, handleID):
        for h in self.handles:
            if h.get_id() == handleID:
                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
    # calcuates the maximum amount of time it is able to sleep
    # for in poll() pending file handle events.
    #
    # It then goes into the poll() sleep.
    #
    # When poll() returns, there will zero or more file handle
    # events which need to be dispatched to registered callbacks
    # It may also be time to fire some periodic timers.
    #
    # Due to the coarse granularity of schedular timeslices, if
    # we ask for a sleep of 500ms in order to satisfy a timer, we
J
Ján Tomko 已提交
172
    # may return up to 1 schedular timeslice early. So even though
173 174 175 176 177 178 179
    # our sleep timeout was reached, the registered timer may not
    # technically be at its expiry point. This leads to us going
    # back around the loop with a crazy 5ms sleep. So when checking
    # if timeouts are due, we allow a margin of 20ms, to avoid
    # these pointless repeated tiny sleeps.
    def run_once(self):
        sleep = -1
180
        self.runningPoll = True
181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207
        try:
            next = self.next_timeout()
            debug("Next timeout due at %d" % next)
            if next > 0:
                now = int(time.time() * 1000)
                if now >= next:
                    sleep = 0
                else:
                    sleep = (next - now) / 1000.0

            debug("Poll with a sleep of %d" % sleep)
            events = self.poll.poll(sleep)

            # Dispatch any file handle events that occurred
            for (fd, revents) in events:
                # See if the events was from the self-pipe
                # telling us to wakup. if so, then discard
                # the data just continue
                if fd == self.pipetrick[0]:
                    self.pendingWakeup = False
                    data = os.read(fd, 1)
                    continue

                h = self.get_handle_by_fd(fd)
                if h:
                    debug("Dispatch fd %d handle %d events %d" % (fd, h.get_id(), revents))
                    h.dispatch(self.events_from_poll(revents))
208

209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227
            now = int(time.time() * 1000)
            for t in self.timers:
                interval = t.get_interval()
                if interval < 0:
                    continue

                want = t.get_last_fired() + interval
                # Deduct 20ms, since scheduler timeslice
                # means we could be ever so slightly early
                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()

        except (os.error, select.error), e:
            if e.args[0] != errno.EINTR:
                raise
        finally:
            self.runningPoll = False
228

229 230 231 232 233 234 235 236

    # Actually the event loop forever
    def run_loop(self):
        self.quit = False
        while not self.quit:
            self.run_once()

    def interrupt(self):
237 238 239
        if self.runningPoll and not self.pendingWakeup:
            self.pendingWakeup = True
            os.write(self.pipetrick[1], 'c')
240 241 242 243 244 245 246 247 248


    # 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
    # used to later update/remove it
    def add_handle(self, fd, events, cb, opaque):
        handleID = self.nextHandleID + 1
        self.nextHandleID = self.nextHandleID + 1
249

250 251
        h = self.virEventLoopPureHandle(handleID, fd, events, cb, opaque)
        self.handles.append(h)
252

253 254
        self.poll.register(fd, self.events_to_poll(events))
        self.interrupt()
255

256
        debug("Add handle %d fd %d events %d" % (handleID, fd, events))
257

258
        return handleID
259

260 261 262 263 264 265 266 267 268 269 270 271 272
    # Registers a new timer with periodic expiry at 'interval' ms,
    # firing cb() each time the timer expires. If 'interval' is -1,
    # then the timer is registered, but not enabled
    # Returns a unique integer identier for this handle, that should be
    # used to later update/remove it
    def add_timer(self, interval, cb, opaque):
        timerID = self.nextTimerID + 1
        self.nextTimerID = self.nextTimerID + 1

        h = self.virEventLoopPureTimer(timerID, interval, cb, opaque)
        self.timers.append(h)
        self.interrupt()

273
        debug("Add timer %d interval %d" % (timerID, interval))
274 275 276 277 278 279 280 281 282 283 284 285

        return timerID

    # Change the set of events to be monitored on the file handle
    def update_handle(self, handleID, events):
        h = self.get_handle_by_id(handleID)
        if h:
            h.set_events(events)
            self.poll.unregister(h.get_fd())
            self.poll.register(h.get_fd(), self.events_to_poll(events))
            self.interrupt()

286
            debug("Update handle %d fd %d events %d" % (handleID, h.get_fd(), events))
287 288 289 290 291

    # Change the periodic frequency of the timer
    def update_timer(self, timerID, interval):
        for h in self.timers:
            if h.get_id() == timerID:
292
                h.set_interval(interval)
293 294
                self.interrupt()

295
                debug("Update timer %d interval %d"  % (timerID, interval))
296 297 298 299 300 301 302 303
                break

    # Stop monitoring for events on the file handle
    def remove_handle(self, handleID):
        handles = []
        for h in self.handles:
            if h.get_id() == handleID:
                self.poll.unregister(h.get_fd())
304
                debug("Remove handle %d fd %d" % (handleID, h.get_fd()))
305 306 307 308 309 310 311 312 313 314 315
            else:
                handles.append(h)
        self.handles = handles
        self.interrupt()

    # Stop firing the periodic timer
    def remove_timer(self, timerID):
        timers = []
        for h in self.timers:
            if h.get_id() != timerID:
                timers.append(h)
316
                debug("Remove timer %d" % timerID)
317 318 319 320 321 322 323 324 325 326 327
        self.timers = timers
        self.interrupt()

    # Convert from libvirt event constants, to poll() events constants
    def events_to_poll(self, events):
        ret = 0
        if events & libvirt.VIR_EVENT_HANDLE_READABLE:
            ret |= select.POLLIN
        if events & libvirt.VIR_EVENT_HANDLE_WRITABLE:
            ret |= select.POLLOUT
        if events & libvirt.VIR_EVENT_HANDLE_ERROR:
328
            ret |= select.POLLERR
329
        if events & libvirt.VIR_EVENT_HANDLE_HANGUP:
330
            ret |= select.POLLHUP
331 332 333 334
        return ret

    # Convert from poll() event constants, to libvirt events constants
    def events_from_poll(self, events):
335
        ret = 0
336
        if events & select.POLLIN:
337
            ret |= libvirt.VIR_EVENT_HANDLE_READABLE
338
        if events & select.POLLOUT:
339
            ret |= libvirt.VIR_EVENT_HANDLE_WRITABLE
340
        if events & select.POLLNVAL:
341
            ret |= libvirt.VIR_EVENT_HANDLE_ERROR
342
        if events & select.POLLERR:
343
            ret |= libvirt.VIR_EVENT_HANDLE_ERROR
344
        if events & select.POLLHUP:
345 346
            ret |= libvirt.VIR_EVENT_HANDLE_HANGUP
        return ret
347 348 349 350 351 352 353 354


###########################################################################
# Now glue an instance of the general event loop into libvirt's event loop
###########################################################################

# This single global instance of the event loop wil be used for
# monitoring libvirt events
355
eventLoop = virEventLoopPure()
356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408

# This keeps track of what thread is running the event loop,
# (if it is run in a background thread)
eventLoopThread = None


# These next set of 6 methods are the glue between the official
# libvirt events API, and our particular impl of the event loop
#
# There is no reason why the 'virEventLoopPure' has to be used.
# An application could easily may these 6 glue methods hook into
# another event loop such as GLib's, or something like the python
# Twisted event framework.

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 virEventLoopPureRegister():
    libvirt.virEventRegisterImpl(virEventAddHandleImpl,
                                 virEventUpdateHandleImpl,
                                 virEventRemoveHandleImpl,
                                 virEventAddTimerImpl,
                                 virEventUpdateTimerImpl,
                                 virEventRemoveTimerImpl)

# Directly run the event loop in the current thread
def virEventLoopPureRun():
    global eventLoop
    eventLoop.run_loop()

409 410 411 412
def virEventLoopNativeRun():
    while True:
        libvirt.virEventRunDefaultImpl()

413 414 415 416 417 418 419 420
# Spawn a background thread to run the event loop
def virEventLoopPureStart():
    global eventLoopThread
    virEventLoopPureRegister()
    eventLoopThread = threading.Thread(target=virEventLoopPureRun, name="libvirtEventLoop")
    eventLoopThread.setDaemon(True)
    eventLoopThread.start()

421 422 423 424 425 426 427
def virEventLoopNativeStart():
    global eventLoopThread
    libvirt.virEventRegisterDefaultImpl()
    eventLoopThread = threading.Thread(target=virEventLoopNativeRun, name="libvirtEventLoop")
    eventLoopThread.setDaemon(True)
    eventLoopThread.start()

428 429 430 431

##########################################################################
# Everything that now follows is a simple demo of domain lifecycle events
##########################################################################
432
def eventToString(event):
433 434
    eventStrings = ( "Defined",
                     "Undefined",
435 436 437
                     "Started",
                     "Suspended",
                     "Resumed",
438
                     "Stopped",
J
Jiri Denemark 已提交
439
                     "Shutdown",
440 441
                     "PMSuspended",
                     "Crashed" )
442
    return eventStrings[event]
443

444 445 446
def detailToString(event, detail):
    eventStrings = (
        ( "Added", "Updated" ),
447
        ( "Removed", ),
448
        ( "Booted", "Migrated", "Restored", "Snapshot", "Wakeup" ),
449
        ( "Paused", "Migrated", "IOError", "Watchdog", "Restored", "Snapshot", "API error" ),
450
        ( "Unpaused", "Migrated", "Snapshot" ),
451
        ( "Shutdown", "Destroyed", "Crashed", "Migrated", "Saved", "Failed", "Snapshot"),
452
        ( "Finished", ),
453 454
        ( "Memory", "Disk" ),
        ( "Panicked", )
455 456 457
        )
    return eventStrings[event][detail]

458
def myDomainEventCallback1 (conn, dom, event, detail, opaque):
459 460 461
    print "myDomainEventCallback1 EVENT: Domain %s(%s) %s %s" % (dom.name(), dom.ID(),
                                                                 eventToString(event),
                                                                 detailToString(event, detail))
462

463
def myDomainEventCallback2 (conn, dom, event, detail, opaque):
464 465 466
    print "myDomainEventCallback2 EVENT: Domain %s(%s) %s %s" % (dom.name(), dom.ID(),
                                                                 eventToString(event),
                                                                 detailToString(event, detail))
467

468 469 470 471 472 473 474 475 476 477 478 479 480 481 482
def myDomainEventRebootCallback(conn, dom, opaque):
    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)

def myDomainEventWatchdogCallback(conn, dom, action, opaque):
    print "myDomainEventWatchdogCallback: Domain %s(%s) %d" % (dom.name(), dom.ID(), action)

def myDomainEventIOErrorCallback(conn, dom, srcpath, devalias, action, opaque):
    print "myDomainEventIOErrorCallback: Domain %s(%s) %s %s %d" % (dom.name(), dom.ID(), srcpath, devalias, action)

def myDomainEventGraphicsCallback(conn, dom, phase, localAddr, remoteAddr, authScheme, subject, opaque):
    print "myDomainEventGraphicsCallback: Domain %s(%s) %d %s" % (dom.name(), dom.ID(), phase, authScheme)

483 484 485
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)
486 487 488
def myDomainEventTrayChangeCallback(conn, dom, devAlias, reason, opaque):
    print "myDomainEventTrayChangeCallback: Domain %s(%s) tray change devAlias: %s reason: %s" % (
            dom.name(), dom.ID(), devAlias, reason)
O
Osier Yang 已提交
489 490 491
def myDomainEventPMWakeupCallback(conn, dom, reason, opaque):
    print "myDomainEventPMWakeupCallback: Domain %s(%s) system pmwakeup" % (
            dom.name(), dom.ID())
O
Osier Yang 已提交
492 493 494
def myDomainEventPMSuspendCallback(conn, dom, reason, opaque):
    print "myDomainEventPMSuspendCallback: Domain %s(%s) system pmsuspend" % (
            dom.name(), dom.ID())
495
def myDomainEventBalloonChangeCallback(conn, dom, actual, opaque):
496
    print "myDomainEventBalloonChangeCallback: Domain %s(%s) %d" % (dom.name(), dom.ID(), actual)
497 498 499
def myDomainEventPMSuspendDiskCallback(conn, dom, reason, opaque):
    print "myDomainEventPMSuspendDiskCallback: Domain %s(%s) system pmsuspend_disk" % (
            dom.name(), dom.ID())
500 501 502
def myDomainEventDeviceRemovedCallback(conn, dom, dev, opaque):
    print "myDomainEventDeviceRemovedCallback: Domain %s(%s) device removed: %s" % (
            dom.name(), dom.ID(), dev)
503 504 505 506 507 508 509 510 511 512

run = True

def myConnectionCloseCallback(conn, reason, opaque):
    reasonStrings = (
        "Error", "End-of-file", "Keepalive", "Client",
        )
    print "myConnectionCloseCallback: %s: %s" % (conn.getURI(), reasonStrings[reason])
    run = False

513
def usage(out=sys.stderr):
514
    print >>out, "usage: "+os.path.basename(sys.argv[0])+" [-hdl] [uri]"
515
    print >>out, "   uri will default to qemu:///system"
516 517 518
    print >>out, "   --help, -h   Print this help message"
    print >>out, "   --debug, -d  Print debug output"
    print >>out, "   --loop, -l   Toggle event-loop-implementation"
519 520 521

def main():
    try:
522
        opts, args = getopt.getopt(sys.argv[1:], "hdl", ["help", "debug", "loop"])
523 524 525 526 527 528 529
    except getopt.GetoptError, err:
        # print help information and exit:
        print str(err) # will print something like "option -a not recognized"
        usage()
        sys.exit(2)
    for o, a in opts:
        if o in ("-h", "--help"):
530
            usage(sys.stdout)
531
            sys.exit()
532 533 534 535 536 537
        if o in ("-d", "--debug"):
            global do_debug
            do_debug = True
        if o in ("-l", "--loop"):
            global use_pure_python_event_loop
            use_pure_python_event_loop ^= True
538

P
Philipp Hahn 已提交
539 540
    if len(args) >= 1:
        uri = args[0]
541 542 543 544 545
    else:
        uri = "qemu:///system"

    print "Using uri:" + uri

546
    # Run a background thread with the event loop
547 548 549 550
    if use_pure_python_event_loop:
        virEventLoopPureStart()
    else:
        virEventLoopNativeStart()
551

552
    vc = libvirt.openReadOnly(uri)
553

554 555 556 557 558 559 560 561
    # Close connection on exit (to test cleanup paths)
    old_exitfunc = getattr(sys, 'exitfunc', None)
    def exit():
        print "Closing " + str(vc)
        vc.close()
        if (old_exitfunc): old_exitfunc()
    sys.exitfunc = exit

562 563
    vc.registerCloseCallback(myConnectionCloseCallback, None)

564 565
    #Add 2 callbacks to prove this works with more than just one
    vc.domainEventRegister(myDomainEventCallback1,None)
566 567 568 569 570 571
    vc.domainEventRegisterAny(None, libvirt.VIR_DOMAIN_EVENT_ID_LIFECYCLE, myDomainEventCallback2, None)
    vc.domainEventRegisterAny(None, libvirt.VIR_DOMAIN_EVENT_ID_REBOOT, myDomainEventRebootCallback, None)
    vc.domainEventRegisterAny(None, libvirt.VIR_DOMAIN_EVENT_ID_RTC_CHANGE, myDomainEventRTCChangeCallback, None)
    vc.domainEventRegisterAny(None, libvirt.VIR_DOMAIN_EVENT_ID_IO_ERROR, myDomainEventIOErrorCallback, None)
    vc.domainEventRegisterAny(None, libvirt.VIR_DOMAIN_EVENT_ID_WATCHDOG, myDomainEventWatchdogCallback, None)
    vc.domainEventRegisterAny(None, libvirt.VIR_DOMAIN_EVENT_ID_GRAPHICS, myDomainEventGraphicsCallback, None)
572
    vc.domainEventRegisterAny(None, libvirt.VIR_DOMAIN_EVENT_ID_DISK_CHANGE, myDomainEventDiskChangeCallback, None)
573
    vc.domainEventRegisterAny(None, libvirt.VIR_DOMAIN_EVENT_ID_TRAY_CHANGE, myDomainEventTrayChangeCallback, None)
O
Osier Yang 已提交
574
    vc.domainEventRegisterAny(None, libvirt.VIR_DOMAIN_EVENT_ID_PMWAKEUP, myDomainEventPMWakeupCallback, None)
O
Osier Yang 已提交
575
    vc.domainEventRegisterAny(None, libvirt.VIR_DOMAIN_EVENT_ID_PMSUSPEND, myDomainEventPMSuspendCallback, None)
576
    vc.domainEventRegisterAny(None, libvirt.VIR_DOMAIN_EVENT_ID_BALLOON_CHANGE, myDomainEventBalloonChangeCallback, None)
577
    vc.domainEventRegisterAny(None, libvirt.VIR_DOMAIN_EVENT_ID_PMSUSPEND_DISK, myDomainEventPMSuspendDiskCallback, None)
578
    vc.domainEventRegisterAny(None, libvirt.VIR_DOMAIN_EVENT_ID_DEVICE_REMOVED, myDomainEventDeviceRemovedCallback, None)
579

580 581
    vc.setKeepAlive(5, 3)

582 583 584 585
    # The rest of your app would go here normally, but for sake
    # of demo we'll just go to sleep. The other option is to
    # run the event loop in your main thread if your app is
    # totally event based.
586
    while run:
587 588
        time.sleep(1)

589 590 591

if __name__ == "__main__":
    main()