# This file is part of CloudControl. # # CloudControl is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # CloudControl is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with CloudControl. If not, see . """Helpers for libvirt.""" import logging from itertools import chain, imap, count from StringIO import StringIO from xml.etree import cElementTree as et import pyev import libvirt from cloudcontrol.common.client.tags import Tag from cloudcontrol.node.utils import Singleton from cloudcontrol.node.exc import PoolStorageError 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. """ # singletion usage is very important because libvirt.virEventRegisterImpl # would segfaults on libvirt 0.8.X __metaclass__ = Singleton def __init__(self, loop): """ :param loop: pyev loop instance """ self.loop = loop self.handle_id = count(1) self.timer_id = count(1) 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 :py:meth:`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() class StorageIndex(object): """Keep an index of all storage volume paths.""" def __init__(self, handler, lv_con): """ :param handler: Hypervisor handler instance :param lv_con: Libvirt connection """ self.handler = handler self.lv_con = lv_con self.storages = dict( (s.name, s) for s in imap( Storage, imap( lv_con.storagePoolLookupByName, chain( lv_con.listDefinedStoragePools(), lv_con.listStoragePools(), ), ), ), ) self.paths = None self.update_path_index() def update(self): """Update storage pools and volumes.""" # go through all storage pools and check if it is already in the index for lv_storage in imap( self.lv_con.storagePoolLookupByName, chain( self.lv_con.listDefinedStoragePools(), self.lv_con.listStoragePools(), ), ): if lv_storage.name() in self.storages: # update self.storages[lv_storage.name()].update() else: # add storage pool s = Storage(lv_storage) self.storages[s.name] = s # add tags self.handler.tag_db.add_tags(( Tag('sto%s_state' % s.name, lambda: s.state, 5, 5), Tag('sto%s_size' % s.name, lambda: s.capacity, 5, 5), Tag('sto%s_free' % s.name, lambda: s.available, 5, 5), Tag('sto%s_used' % s.name, lambda: s.capacity - s.available, 5, 5), Tag('sto%s_type' % s.name, lambda: s.type, 5, 5), )) self.update_path_index() def update_path_index(self): self.paths = dict( (v.path, v) for v in chain.from_iterable(imap( lambda s: s.volumes.itervalues(), self.storages.itervalues(), )), ) def get_volume(self, path): return self.paths.get(path) def get_volume_by_pool(self, pool, vol): storage = self.get_storage(pool) return storage.volumes.get(vol) def get_storage(self, name): return self.storages.get(name) def create_volume(self, pool_name, volume_name, capacity): """Create a new volume in the storage pool. :param str name: name for the volume :param int capacity: size for the volume """ # get volume logger.debug('asked pool %s', pool_name) logger.debug('Pool state %s', self.storages) try: pool = self.storages[pool_name] except KeyError: raise PoolStorageError('Invalid pool name') if pool is None: raise Exception('Storage pool not found') try: new_volume = pool.lv_storage.createXML(""" %s %d """ % (volume_name, capacity), 0) except libvirt.libvirtError: logger.exception('Error while creating volume') raise new_volume = Volume(new_volume) # if success add the volume to the index self.paths[new_volume.path] = new_volume # and also to its storage pool self.storages[new_volume.storage].volumes[new_volume.name] = new_volume return new_volume def delete_volume(self, pool_name, volume_name): """Delete a volume in the givent storage pool. :param str pool_name: name for the storage pool :param str volume_name: name for the volume """ # get volume try: pool = self.storages[pool_name] except KeyError: raise PoolStorageError('Invalid pool name') try: volume = pool.volumes[volume_name] except KeyError: raise PoolStorageError('Invalid volume name') # delete from index del self.paths[volume.path] del self.storages[pool_name].volumes[volume_name] # delete volume try: volume.lv_volume.delete(0) except libvirt.libvirtError: logger.exception('Error while deleting volume') raise class Storage(object): """Storage abstraction.""" def __init__(self, lv_storage): """ :param lv_storage: Libvirt pool storage instance """ self.uuid = lv_storage.UUID() self.name = lv_storage.name() self.lv_storage = lv_storage self.state, self.capacity = None, None self.allocation, self.available = None, None self.type = et.ElementTree().parse( StringIO(lv_storage.XMLDesc(0))).get('type') self.volumes = dict((v.name, v) for v in imap( Volume, (lv_storage.storageVolLookupByName(n) for n in lv_storage.listVolumes()), )) self.update_attr() def update(self): self.update_attr() # update volumes for vol_name in self.lv_storage.listVolumes(): if vol_name in self.volumes: # update volume self.volumes[vol_name].update() else: # add volume v = Volume(self.lv_storage.storageVolLookupByName(vol_name)) self.volumes[v.name] = v def update_attr(self): self.state, self.capacity, self.allocation, self.available = self.lv_storage.info() self.state = STORAGE_STATES[self.state] self.type = et.ElementTree().parse( StringIO(self.lv_storage.XMLDesc(0))).get('type') class Volume(object): """Volume abstraction.""" def __init__(self, lv_volume): """ :param lv_volume: Libvirt volume instance """ self.storage = lv_volume.storagePoolLookupByVolume().name() self.path = lv_volume.path() self.name = lv_volume.name() self.capacity, self.allocation = None, None self.lv_volume = lv_volume self.update() def update(self): self.capacity, self.allocation = self.lv_volume.info()[1:]