"""Helpers for libvirt.""" import logging from itertools import chain import pyev import libvirt from cloudcontrol.node.utils import Singleton logger = logging.getLogger(__name__) #: corresponding name for enum state of libvirt domains # see http://libvirt.org/html/libvirt-libvirt.html#virDomainState DOMAIN_STATES = ( 'stopped', # 0 no state 'running', # 1 running 'blocked', # 2 blocked 'paused', # 3 paused 'running', # 4 shutdown 'stopped', # 5 shuttoff 'crashed', # 6 crashed 'suspended', # 7 suspended ) STORAGE_STATES = ( 'inactive', 'building', 'running', 'degraded', 'inaccessible', '???', # 5 ) #: libvirt events EVENTS = ( 'Added', 'Removed', 'Started', 'Suspended', 'Resumed', 'Stopped', 'Saved', 'Restored', ) # following event loop implementation was inspired by libvirt python example # but updated to work with libev class LoopHandler(object): """This class contains the data we need to track for a single file handle. """ def __init__(self, loop, handle, fd, events, cb, opaque): self.handle = handle self.fd = fd self._events = self.virt_to_ev(events) self._cb = cb self.opaque = opaque self.watcher = loop.io(self.fd, self._events, self.ev_cb, None, pyev.EV_MAXPRI) def ev_to_virt(self, events): """Convert libev events into libvirt one.""" result = 0 if events & pyev.EV_READ: result |= libvirt.VIR_EVENT_HANDLE_READABLE if events & pyev.EV_WRITE: result |= libvirt.VIR_EVENT_HANDLE_WRITABLE return result def virt_to_ev(self, events): """Convert libvirt event to libev one.""" result = 0 if events & (libvirt.VIR_EVENT_HANDLE_READABLE | libvirt.VIR_EVENT_HANDLE_ERROR | libvirt.VIR_EVENT_HANDLE_HANGUP): result |= pyev.EV_READ if events & libvirt.VIR_EVENT_HANDLE_WRITABLE: result |= pyev.EV_WRITE return result def _set(self): self.watcher.stop() if self._events != 0: self.watcher.set(self.fd, self._events) self.watcher.start() @property def events(self): return self._events @events.setter def events(self, events): self._events = self.virt_to_ev(events) self._set() def start(self): self.watcher.start() def stop(self): self.watcher.stop() def ev_cb(self, watcher, revents): # convert events events = self.ev_to_virt(revents) self._cb(self.handle, self.watcher.fd, events, self.opaque[0], self.opaque[1]) class LoopTimer(object): """This class contains the data we need to track for a single periodic timer. """ def __init__(self, loop, timer, interval, cb, opaque): self.timer = timer self._interval = float(interval) self._cb = cb self.opaque = opaque self.watcher = None self.loop = loop self._set() def _set(self): self.stop() if self._interval >= 0.: # libvirt sends us interval == -1 self.watcher = self.loop.timer(self._interval, self._interval, self.ev_cb, None, pyev.EV_MAXPRI) else: self.watcher = None self.start() @property def interval(self): return self._interval @interval.setter def interval(self, value): self._interval = float(value) self._set() def start(self): if self.watcher is not None: self.watcher.start() def stop(self): if self.watcher is not None: self.watcher.stop() def ev_cb(self, *args): self._cb(self.timer, self.opaque[0], self.opaque[1]) class EventLoop(object): """This class is used as an interface between the libvirt event handling and the main pyev loop. It cannot be used from other threads. """ __metaclass__ = Singleton def __init__(self, loop): """ :param loop: pyev loop instance """ self.loop = loop def counter(): count = 0 while True: yield count count += 1 self.handle_id = counter() self.timer_id = counter() self.handles = dict() self.timers = dict() # This tells libvirt what event loop implementation it # should use libvirt.virEventRegisterImpl( self.add_handle, self.update_handle, self.remove_handle, self.add_timer, self.update_timer, self.remove_timer, ) def add_handle(self, fd, events, cb, opaque): """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. Note: unlike in the libvirt example, we don't use an interrupt trick as we run everything in the same thread, furthermore, calling start watcher method from a different thread could be dangerous. """ handle_id = self.handle_id.next() h = LoopHandler(self.loop, handle_id, fd, events, cb, opaque) h.start() self.handles[handle_id] = h # logger.debug('Add handle %d fd %d events %d', handle_id, fd, events) return handle_id def add_timer(self, interval, cb, opaque): """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. Note: same note as for :method:`add_handle` applies here """ timer_id = self.timer_id.next() h = LoopTimer(self.loop, timer_id, interval, cb, opaque) h.start() self.timers[timer_id] = h # logger.debug('Add timer %d interval %d', timer_id, interval) return timer_id def update_handle(self, handle_id, events): """Change the set of events to be monitored on the file handle. """ h = self.handles.get(handle_id) if h: h.events = events # logger.debug('Update handle %d fd %d events %d', handle_id, h.fd, events) def update_timer(self, timer_id, interval): """Change the periodic frequency of the timer. """ t = self.timers.get(timer_id) if t: t.interval = interval def remove_handle(self, handle_id): """Stop monitoring for events on the file handle. """ h = self.handles.pop(handle_id, None) if h: h.stop() # logger.debug('Remove handle %d', handle_id) def remove_timer(self, timer_id): """Stop firing the periodic timer. """ t = self.timers.pop(timer_id, None) if t: t.stop() # logger.debug('Remove timer %d', timer_id) def stop(self): for handl in chain(self.handles.itervalues(), self.timers.itervalues()): handl.stop() self.handles = dict() self.timers = dict()