"cloudcontrol/node/utils.py" did not exist on "a0e122653a50c0007ec23edee71b18a6d3c5c30c"
Newer
Older
# 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 <http://www.gnu.org/licenses/>.
from itertools import chain, imap, count
from StringIO import StringIO
from xml.etree import cElementTree as et
from cloudcontrol.node.utils import Singleton
from cloudcontrol.node.exc import PoolStorageError
#: 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
'paused', # 3 paused
'running', # 4 shutdown
'stopped', # 5 shuttoff
'crashed', # 6 crashed
'suspended', # 7 suspended
STORAGE_STATES = (
'inactive',
'building',
'running',
'degraded',
'inaccessible',
'???', # 5
)
EVENTS = (
'Added',
'Removed',
'Started',
'Suspended',
'Resumed',
'Stopped',
'Saved',
'Restored',
)
# following event loop implementation was inspired by libvirt python example
"""This class contains the data we need to track for a single file handle.
"""
def __init__(self, loop, handle, fd, events, cb, opaque):
self._events = self.virt_to_ev(events)
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
if self._events != 0:
self.watcher.set(self.fd, self._events)
self.watcher.start()
@property
def events(self):
return self._events
self._events = self.virt_to_ev(events)
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])
"""This class contains the data we need to track for a single periodic
timer.
"""
def __init__(self, loop, timer, interval, cb, opaque):
self._interval = float(interval)
self._cb = cb
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)
@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])
"""This class is used as an interface between the libvirt event handling and
the main pyev loop.
# singletion usage is very important because libvirt.virEventRegisterImpl
# would segfaults on libvirt 0.8.X
self.handle_id = count(1)
self.timer_id = count(1)
# 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.
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)
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
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)
"""Change the set of events to be monitored on the file handle.
"""
# logger.debug('Update handle %d fd %d events %d', handle_id, h.fd, events)
"""Change the periodic frequency of the timer.
"""
t = self.timers.get(timer_id)
if t:
t.interval = interval
"""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."""
SHARED_TYPES = ['rbd']
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.paths = None
"""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(retry)
else:
# add storage pool
storage_type = et.ElementTree().parse(StringIO(lv_storage.XMLDesc(0))).get('type')
if storage_type in self.SHARED_TYPES:
s = SharedStorage(lv_storage)
else:
s = Storage(lv_storage)
self.storages[s.name] = s
# add tags
self.handler.tag_db.add_tags((
Tag('sto%s_state' % s.name, partial(lambda x: x.state, s), 5, 5),
Tag('sto%s_size' % s.name, partial(lambda x: x.capacity, s), 5, 5),
Tag('sto%s_free' % s.name, partial(lambda x: x.available, s), 5, 5),
Tag('sto%s_used' % s.name,
partial(lambda x: x.capacity - x.available
if x.available is not None and x.capacity is not None else None, s), 5, 5),
Tag('sto%s_type' % s.name, partial(lambda x: x.type, s), 5, 5),
partial(lambda x: ' '.join(x.volumes) if x.volumes and not x.is_shared else None, s),
5, 5),
Tag('sto%s_ratio' % s.name,
partial(lambda x: '%0.2f' % (1 - float(x.available) / x.capacity)
if x.available is not None and x.capacity is not None else None, s), 5, 5),
Tag('sto%s_shared' % s.name, partial(lambda x: {True: 'yes', False: 'no'}[s.is_shared], s)),
# Add a special storage for standalone drives:
if '_standalone' not in self.storages:
self.storages['_standalone'] = DummyStorage('_standalone')
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)
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
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("""<volume>
<name>%s</name>
<capacity>%d</capacity>
</volume>""" % (volume_name, capacity), 0)
except libvirt.libvirtError:
logger.exception('Error while creating volume')
raise
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
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 = {}
self.update()
return False
def update(self, retry=1):
for _ in xrange(retry):
try:
self.lv_storage.refresh()
except libvirt.libvirtError as err:
logger.warning('Unable to refresh storage %s: %s', self.name, err)
time.sleep(self.REFRESH_RETRY_INTERVAL)
else:
break
try:
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, self.lv_storage.storageVolLookupByName(vol_name))
self.volumes[v.name] = v
except libvirt.libvirtError as err:
logger.warning('Unable to update storage %s: %s', self.name, err)
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 DummyStorageVolumeDispenser(object):
def __init__(self, storage):
self.storage = storage
def get(self, name):
return DummyVolume(self.storage, name)
def itervalues(self):
return iter([])
class SharedStorage(Storage):
"""Shared storage abstraction."""
def __init__(self, lv_storage):
"""
:param lv_storage: Libvirt pool storage instance
"""
super(SharedStorage, self).__init__(lv_storage)
self.volumes = DummyStorageVolumeDispenser(self)
@property
def is_shared(self):
return True
def update(self, retry=1):
pass # Do nothing.
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
class DummyStorage(Storage):
"""Dummy storage abstraction."""
def __init__(self, name, shared=True):
"""
:param lv_storage: Libvirt pool storage instance
"""
self.uuid = None
self.name = name
self.shared = shared
self.state, self.capacity = None, None
self.allocation, self.available = None, None
self.type = 'dummy'
self.volumes = DummyStorageVolumeDispenser(self)
@property
def is_shared(self):
return self.shared
def update(self, retry=1):
pass # Do nothing
class Volume(object):
"""Volume abstraction."""
def __init__(self, storage, lv_volume):
"""
:param lv_volume: Libvirt volume instance
"""
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:]
class DummyVolume(object):
"""Dummy volume abstraction."""
def __init__(self, storage, name):
self.storage = storage
self.path, self.capacity, self.allocation = None, None, None
self.name = name
self.lv_volume = None
def update(self):
pass # Do nothing.
class BoundVolumeProxy(object):
"""Proxy object to an existing Volume when its bound to a VM."""
def __init__(self, volume, device, bus):
self.volume = volume
self.device = device
self.bus = bus
def __getattr__(self, name):
return self.volume.__getattribute__(name)