Skip to content
libvirtwrapper.py 17.6 KiB
Newer Older
Benziane Chakib's avatar
Benziane Chakib committed
#TODO: vm informations gathering !!!

import libvirt
import sys
# we use psutils to get host informations we can't get with
Benziane Chakib's avatar
Benziane Chakib committed
# libvirt
import psutil
from interface import *
from exceptions import *
from time import sleep

###
# Defined constants
###

##
# Libvirt
##

#
KVM_LIBVIRT_SESSION = 'qemu:///system'
XEN_LIBVIRT_SESSION = 'xen:///'

#########
## States
#########

# Virtual Machines

VM_STATUS = (
    'No state',
    'Running',
    'Blocked on resource',
    'Paused',
    'Shutting down ...',
    'Shutdown',
    'Crashed'
    )

VM_START_STATES = {
    'running': 1,
    'paused' : 0
    }

# Storage Pools

POOL_STATE = (
    'Not Running',
    'Initializing pool, not available',
    'Running normally',
    'Running degraded',
    'Running, but not accessible'
    )


#########################
## Pool informations tags
#########################

POOL_NAME = 'pool'
POOL_STATUS = 'pstatus'
POOL_TOTAL_SIZE = 'psize'
POOL_FREE_SPACE = 'pfree_space'
POOL_USED_SPACE = 'pused_space'
POOL_NUM_VOLUMES = 'num_vols'
POOL_BACKEND = 'backend'

DEFAULT_VM_START = VM_START_STATES['running']

Benziane Chakib's avatar
Benziane Chakib committed
# Pretty size outputs
##

MEGABYTE_DIV = 1024 * 1024
GIGABYTE_DIV = 1024 * 1024 * 1024
KILOBYTE_DIV = 1024

class LibvirtVm(VM):
    '''
    Libvirt api access to Virtual Machine actions
    All libvirt.virDomain calls must stay in this class

    :param domain: a libvirt domain instance
    :type domain: :class:`libvirt.virDomain`
    :param hypervisor: a hypervisor reference object
    :type hypervisor: :class:`LibvirtHypervisor`
    '''
    _print_header = True

    def __init__(self, domain, hypervisor):
        '''
        '''
        super(LibvirtVm, self).__init__()
        if isinstance(domain, libvirt.virDomain):
            self._domain = domain
            self.vm_info = {}
            self._find_pid()
        else:
            raise TypeError('Need virDomain object given %s' % type(domain))
        self.set_hypervisor(hypervisor)

    def __cmp__(self, other):
        '''
        We overload the compare method so that two virtual machines are
        identical if they are bound to the same domain object
        '''
        return cmp(self._domain, other._domain)

    def _find_pid(self):
        '''
        Finds the PID of the current vm
        '''
        result =  find_process_id(self._domain.UUIDString())
        if result:
            self._pid = int(result.pop())
        else:
            self._pid = None
    def get_pid(self):
        return self._pid

    def set_hypervisor(self, hypervisor):
        if isinstance(hypervisor, LibvirtHypervisor):
            self._hypervisor = hypervisor
        else:
            raise TypeError('argument type error')

    def get_name(self):
         return self._domain.name()

    def get_hv_name(self):
        return self._hypervisor.get_name()

Benziane Chakib's avatar
Benziane Chakib committed
    def get_used_mem(self):
        return self._domain.info()[2] / KILOBYTE_DIV

    def get_total_mem(self):
        return self._domain.info()[1] / KILOBYTE_DIV

    def get_free_mem(self):
        return (self.get_total_mem() - self.get_used_mem()) / KILOBYTE_DIV

    def get_vcpu(self):
        return self._domain.info()[3]
Benziane Chakib's avatar
Benziane Chakib committed
    def get_cpu_percent(self):
        self._find_pid()
        if self._pid:
            p = psutil.Process(self._pid)
            sleep(0.7)
            res = int(p.get_cpu_percent())
            res = 100 if res > 100 else res
            return res
        else:
            return 0
Benziane Chakib's avatar
Benziane Chakib committed
    def get_status(self):
        return VM_STATUS[self._domain.info()[0]]

    def shutdown(self):
        try:
            self._domain.shutdown()
        except libvirt.libvirtError:
            raise VMError('%s is not running !' % self.get_name())

    def force_poweroff(self):
        try:
            self._domain.destroy()
        except libvirt.libvirtError:
            raise VMError('%s is not running !' % self.get_name())
Benziane Chakib's avatar
Benziane Chakib committed

    def start(self):
        try:
            self._domain.create()
        except libvirt.libvirtError:
            raise VMError('%s is already running !' % self.get_name())

    def suspend(self):
        try:
            self._domain.suspend()
        except libvirt.libvirtError:
            raise VMError('%s is not running !' % self.get_name())
Benziane Chakib's avatar
Benziane Chakib committed

    def resume(self):
        try:
            self._domain.resume()
        except libvirt.libvirtError:
            raise VMError('%s is not running !' % self.get_name())
Benziane Chakib's avatar
Benziane Chakib committed

    def get_uuid(self):
        '''
        Returns the uuid string of the vm
        '''
        return self._domain.UUIDString()

    def __str__(self):
        '''
        LibvirtVm object string representation
        '''
        header = '%-10s | %-10s | %-10s | %-15s | %-10s\n\n' %\
        ('Name', 'UsedMem', 'VCpu', 'CpuUsage', 'Status')
        self._fetch_memory_stats()
        self.get_cpu_stats()
        stats = '%-10s | %-10s | %-10s | %-15s | %-10s' %\
        (self.get_name(), self.used_memory, self.vcpu, self.cpu_use,
                                                    self.get_status())
        return header + stats


class LibvirtHypervisor(Hypervisor):
    '''
    Libvirt api access to Hypervisor actions.
    All libvirt calls to the hypervisor stay in this class

    :param hv_type: This is the hypervisor type, this parameter is
        important as it specifies the hypervisor type used when
        connecting to libvirt.
    :type hv_type: :class:`str`
Benziane Chakib's avatar
Benziane Chakib committed
    .. note::
        hv_type can be any of kvm, xen, ... , other hypervisors, however only
        one connection at a time is allowed
    '''
Benziane Chakib's avatar
Benziane Chakib committed
    def __init__(self, hv_type):
        #FIXME: don't use hard coded hypervisor type in URI
        try:
            if hv_type == 'kvm':
                self._con_handle = libvirt.open(KVM_LIBVIRT_SESSION)
            else:
                raise NotImplemented('or unknown hypervisor type')
        except libvirt.libvirtError as error:
            raise HypervisorError(error, 'libvirt cannot connect to hypervisor')
Thibault VINCENT's avatar
Thibault VINCENT committed
        #build storage objs
        self._storage = LibvirtHVStorage(self)
Benziane Chakib's avatar
Benziane Chakib committed
        #build vm objects
        self._build_vm_list()
        self.hv_info = {}
        self.hv_type = hv_type

    def get_name(self):
        '''
        Returns hypervisor's name
        '''
        return self._con_handle.getHostname()

    def get_hv_type(self):
        return self.hv_type

    def get_hv_version(self):
        return self._con_handle.getVersion()

    def _build_vm_list(self):
        '''
        Builds the lists of all defined vms (running and offline) in the
        current hypervisor
        '''
        self._runing_vm_list = []
        self._offline_vm_list = []
        self._dom_ids = self._con_handle.listDomainsID()
        self._defined_doms = self._con_handle.listDefinedDomains()
        for doms in self._dom_ids:
            vm = self._con_handle.lookupByID(doms)
            self._runing_vm_list.append(LibvirtVm(vm, self))
        for defined_dom in self._defined_doms:
            vm = self._con_handle.lookupByName(defined_dom)
            self._offline_vm_list.append(LibvirtVm(vm, self))
        self._vm_list = []
        self._vm_list = self._runing_vm_list
        self._vm_list.extend(self._offline_vm_list)

    def get_vms(self):
        '''
        Returns a list of :class:`LibvirtVm` objects defining all
        current defined vms in the hypervisor
        '''
        return self._vm_list

    def get_arch_type(self):
        '''
        Get archetcture type of hypervisor
        '''
        return self._con_handle.getInfo()[0]

    def get_nb_cpu(self):
        '''
        Returns number of active cpus in hypervisor
        '''
        return self._con_handle.getInfo()[2]

    def get_cpu_frequency(self):
        '''
        Returns the frequency in MHZ of cpus in the hypervisor
        '''
        return self._con_handle.getInfo()[3]

    def get_nb_threads(self):
        '''
        Number of threads per core
        '''
        return self._con_handle.getInfo()[7]

    def get_free_mem(self):
        return psutil.avail_phymem() / MEGABYTE_DIV

    def get_used_mem(self):
        return psutil.used_phymem() / MEGABYTE_DIV

    def get_total_mem(self):
        return ((psutil.avail_phymem() + psutil.used_phymem()) /
        MEGABYTE_DIV)

    def get_cpu_percent(self):
        return '%2.0f' % psutil.cpu_percent()
Thibault VINCENT's avatar
Thibault VINCENT committed
        
    def get_storage_total_capacity(self):
        capacity = 0
        for pool in self._storage.get_pools().iteritems():
            capacity = capacity + self._storage.get_pool_space_total(pool[1])
        return capacity
Thibault VINCENT's avatar
Thibault VINCENT committed
    
    def get_storage_total_free(self):
        free = 0
        for pool in self._storage.get_pools().iteritems():
            free = free + self._storage.get_pool_space_available(pool[1])
        return free
Thibault VINCENT's avatar
Thibault VINCENT committed
    
    def get_storage_total_used(self):
        used = 0
        for pool in self._storage.get_pools().iteritems():
            used = used + self._storage.get_pool_space_used(pool[1])
        return used
Thibault VINCENT's avatar
Thibault VINCENT committed
    
Benziane Chakib's avatar
Benziane Chakib committed
    def get_status(self):
        raise NotImplementedError()

Thibault VINCENT's avatar
Thibault VINCENT committed

#TODO: finish storage class, make tests,
Benziane Chakib's avatar
Benziane Chakib committed
class LibvirtHVStorage(HVStorage):
    '''
    Base storage class using libvirt api for storage managment
    Storage pools here are libvirt.virStoragePool instances
    '''
    def __init__(self, hypervisor):
        if isinstance(hypervisor, LibvirtHypervisor):
            self.hv_handle = hypervisor
            self._fetch_st_pools()
        else:
Thibault VINCENT's avatar
Thibault VINCENT committed
            raise TypeError('Expected %s given %s' % (LibvirtHypervisor,
Benziane Chakib's avatar
Benziane Chakib committed
                                                        hypervisor))

    def _fetch_st_pools(self):
        '''
        Sets an attribute self._pool_objs containging a list of libvirt
        pool objects bound to the hypervisor
        '''
        pools = []
        pools.extend(self.hv_handle._con_handle.listDefinedStoragePools())
        pools.extend(self.hv_handle._con_handle.listStoragePools())
        self._pools = dict([(p, pool_obj(self.hv_handle._con_handle, p)) \
            for p in pools])
Benziane Chakib's avatar
Benziane Chakib committed
        '''
        Returns a dict of storage pools bound to the host
Benziane Chakib's avatar
Benziane Chakib committed
        '''
    def get_volume_names(self, pool=None):
Benziane Chakib's avatar
Benziane Chakib committed
        '''
        Returns volume names stored in this pool or all pools
Benziane Chakib's avatar
Benziane Chakib committed
        '''
        try:
            if pool is None:
                for pool in self._pools.iteritems():
                    volumes.extend(pool[1].listVolumes())
            else:
                volumes = pool.listVolumes()
        except libvirt.libvirtError as e:
            raise StorageError("Failed to get volume list (%s)" % e)

    def add_volume(self, pool, name, space):
        '''
        Adds a volume to the specified pool

        :param pool: the pool in which to create the volume
        :type pool: :class:`str`
        :param name: name of the new volume
        :type name: :class:`str`
        :param space: size of the new volume in gigabytes
        :type space: :class:`int`
        '''
        xml_desc = """
            <volume>
                <name>%(vol_name)s</name>
                <capacity>%(vol_size)u</capacity>
            </volume>
        """ % {
            "vol_name" : name,
            "vol_size" : space * GIGABYTE_DIV,
            }
        try:
            self._pools[pool].createXML(xml_desc, 0);
        except libvirt.libvirtError as e:
            raise StorageError("Failed to create the volume (%s)" % e)

    def del_volume(self, pool, name, wipe=False):
        '''
        Deletes a volume in the specified pool

        :param pool: the pool in which delete the volume
        :type pool: :class:`str`
        :param name: the name of the volume
        :type name: :class:`str`
        '''
        try:
            vol = pool.storageVolLookupByName(name)
        except libvirt.libvirtError as e:
            raise StorageError("Volume not found (%s)" % e)
        if wipe:
            try:
                vol.wipe(0)
            except libvirt.libvirtError as e:
                raise StorageError("Failed to wipe volume, data integrity"
                    " is unknown (%s)" % e)
        try:
            vol.delete(0)
        except libvirt.libvirtError as e:
            raise StorageError("Failed to delete the volume, but it may be"
                " wiped (%s)" % e)
    def find_space(self, new_space):
        '''
        Tries to find a suitable chunk of space for volume allocation.

        :param new_space: a space size in gigabytes
        :type new_space: :class:`int`
        :return: a :class:`tuple` of best location or :class:`False` if no
            pool is suitable
        for pool in self._pools.iteritems():
                # FIXME, crappy overhead delta
                if new_space * GIGABYTE_DIV <= pool[1].info()[3] - MEGABYTE_DIV:
                    return (pool)
            except libvirt.libvirtError as e:
                raise StorageError("Can't get pool informations (%s)" % e)
    def get_pool_name(self, pool):
        '''
        Returns the name of this pool

        :param pool: the storage pool name
        :type pool: libvirt.`virStoragePool`
        :return: :class:`str` name of the pool
        '''
        try:
            return pool.name()
        except libvirt.libvirtError as e:
            raise StorageError("Can't get pool name (%s)" % e)

    def get_pool_state(self, pool):
        '''
        Returns the running state of the pool

        :param pool: the storage pool name
        :type pool: libvirt.`virStoragePool`
        '''
        try:
            return POOL_STATE[pool.info()[0]]
        except libvirt.libvirtError as e:
            raise StorageError("Can't get pool state (%s)" % e)
Thibault VINCENT's avatar
Thibault VINCENT committed
    def get_pool_space_total(self, pool):
        Returns the storage capacity of this pool in bytes
        
        :param pool: the pool to get information from
        :type pool: :class:`virStoragePool`
        :return: :class:`int` of capacity in bytes
        try:
            return pool.info()[1]
        except libvirt.libvirtError as e:
            raise StorageError("Can't get pool informations (%s)" % e)
Thibault VINCENT's avatar
Thibault VINCENT committed
    def get_pool_space_available(self, pool):
        Returns available space of this storage pool in bytes
        
        :param pool: the pool to get information from
        :type pool: :class:`virStoragePool`
        :return: :class:`int` of available free space in bytes
        try:
            return pool.info()[2]
        except libvirt.libvirtError as e:
            raise StorageError("Can't get pool informations (%s)" % e)
Thibault VINCENT's avatar
Thibault VINCENT committed
    def get_pool_space_used(self, pool):
        Returns current storage pool usage in bytes
        
        :param pool: the pool to get information from
        :type pool: :class:`virStoragePool`
        :return: :class:`int` of used space in bytes
        try:
            return pool.info()[3]
        except libvirt.libvirtError as e:
            raise StorageError("Can't get pool informations (%s)" % e)
    
    def get_volume_path(self, pool, vol_name):
        '''
        Returns the file path to the volume

        :param pool: the storage pool containing this volume
        :type pool: libvirt.`virStoragePool`
        :param volume_name: name of the pool volume
        :type volume_name: :class:`str`
        :return: :class:`str` path to the volume file
        '''
        try:
            vol = pool.storageVolLookupByName(vol_name)
        except libvirt.libvirtError as e:
            raise StorageError("Can't find volume in pool (%s)" % e)
        try:
            return vol.path()
        except libvirt.libvirtError as e:
            raise StorageError("Volume has no path information (%s)" % e)
    def get_volume_allocation(self, pool, vol_name):
        '''
        Returns the pool space used by this volume in bytes

        :param pool: the pool containing this volume from
        :type pool: :class:`virStoragePool`
        :param vol_name: name of the volume to query
        :type vol_name: :class:`str`
        :return: :class:`int` of allocation in bytes
        '''
        try:
            vol = pool.storageVolLookupByName(vol_name)
        except libvirt.libvirtError as e:
            raise StorageError("Volume not found (%s)" % e)
        try:
            return vol.info()[2]
        except libvirt.libvirtError as e:
            raise StorageError("Volume has no allocation information (%s)" % e)

    def get_volume_capacity(self, pool, vol_name):
        '''
        Returns the capacity (usable space) of this volume in bytes

        :param pool: the pool containing this volume from
        :type pool: :class:`virStoragePool`
        :param vol_name: name of the volume to query
        :type vol_name: :class:`str`
        :return: :class:`int` of capacity in bytes
        '''
        try:
            vol = pool.storageVolLookupByName(vol_name)
        except libvirt.libvirtError as e:
            raise StorageError("Volume not found (%s)" % e)
        try:
            return vol.info()[1]
        except libvirt.libvirtError as e:
            raise StorageError("Volume has no capacity information (%s)" % e)
Benziane Chakib's avatar
Benziane Chakib committed

#### Helper functions

def map_process(process):
    '''
    map each process object with it's pid and command line options
    '''
    return (process.pid, process.cmdline)

def find_process_id(uuid):
    '''
    Find the process id of a kvm process gevin an UUID of a virDomain object
    Returns a list of one process id
    '''
    return [p.pid for p in psutil.get_process_list() if
                            p.cmdline.__contains__(uuid)]

def pool_obj(con_handle, pool_name):
    '''
    Creates a libvirt pool object given the name of the pool

    :param con_handle: libvirt connection handle
    :type con_handle: :class:`libvirt.virConnect`
    '''
    return con_handle.storagePoolLookupByName(pool_name)