Skip to content
libvirtwrapper.py 10.5 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 
# 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
    }

SOFT_VM_POWEROFF = True
FORCE_VM_POWEROFF = False

# 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']

## 
# 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_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]
    
    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
    
    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())
            

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

    def suspend(self):
        self._domain.suspend()

    def resume(self):
        self._domain.resume()

    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`
    
    .. note::
        hv_type can be any of kvm, xen, ... , other hypervisors, however only
        one connection at a time is allowed
    '''
    
    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')            
        #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()

    def get_status(self):
        raise NotImplementedError()

#TODO: finish storage class, make tests, 
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:
            raise TypeError('Excpected %s given %s' % (LibvirtHypervisor,
                                                        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 = []
        self._pools.extend(pool_obj(self.hv_handle._con_handle, pool) for pool in pools) 

    def get_storage_pools(self):
        return [pool.name() for pool in self._pools]

    def get_pool_info(self, pool):
        pool_info = {}
        pool_info[POOL_NAME] = pool.name()
        pool_info[POOL_STATUS] = self.get_pool_state(pool)
        pool_info[POOL_TOTAL_SIZE] = self.get_pool_size(pool) / GIGABYTE_DIV 
                                        

        return pool_info

    def get_pool_size(self, pool):
        '''
        Returns total logical size of the pool

        :param pool: the storage pool
        :type pool: :class:`libvirt.virStoragePool`
        '''
        return pool.info()[1]

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

        :param pool: the storage pool name
        :type pool: libvirt.`virStoragePool`
        '''
        return POOL_STATE[pool.info()[0]]
        
            

#### 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)