"""KVM hypervisor support.""" import logging import weakref import libvirt from cloudcontrol.node.hypervisor.lib import ( DOMAIN_STATES, EVENTS, EventLoop as VirEventLoop, StorageIndex, ) from cloudcontrol.node.hypervisor.domains import VirtualMachine logger = logging.getLogger(__name__) # FIXME create abstract base class for any hypervisor class KVM(object): """Container for all hypervisor related state.""" def __init__(self, name, handler): """ :param str name: name of hypervisor instance :param Handler handler: hypervisor handler """ self.handler = weakref.proxy(handler) #: hv attributes self.name = name self.type = u'kvm' # register libvirt error handler libvirt.registerErrorHandler(self.vir_error_cb, None) # libvirt event loop abstraction self.vir_event_loop = VirEventLoop(self.handler.main.evloop) self.vir_con = libvirt.open('qemu:///system') # currently only support KVM # findout storage self.storage = StorageIndex(handler, self.vir_con) logger.debug('Storages: %s', self.storage.paths) #: domains: vms, containers... self.domains = dict() # find defined domains for dom_name in self.vir_con.listDefinedDomains(): dom = self.vir_con.lookupByName(dom_name) self.domains[dom.name()] = VirtualMachine(dom, self) # find started domains for dom_id in self.vir_con.listDomainsID(): dom = self.vir_con.lookupByID(dom_id) self.domains[dom.name()] = VirtualMachine(dom, self) logger.debug('Domains: %s', self.domains) self.vir_con.domainEventRegister(self.vir_cb, None) # TODO find out args def stop(self): self.vir_event_loop.stop() # unregister callback try: self.vir_con.domainEventDeregister(self.vir_cb) except libvirt.libvirtError: # in case the libvirt connection is broken, it will raise the error pass ret = self.vir_con.close() logger.debug('Libvirt still handling %s ref connections', ret) # TODO delet objects def vir_error_cb(self, ctxt, err): """Libvirt error callback. See http://libvirt.org/errors.html for more informations. :param ctxt: arbitrary context data (not needed because context is givent by self :param err: libvirt error code """ logger.error('Libvirt error %s', err) def vir_cb(self, conn, dom, event, detail, opaque): """Callback for libvirt event loop.""" logger.debug('Received event %s on domain %s, detail %s', event, dom.name(), detail) event = EVENTS[event] if event == 'Added': # update Storage pools in case VM has volumes that were created self.storage.update() if dom.name() in self.domains: # sometimes libvirt send us the same event multiple times # this can be the result of a change in the domain configuration # we first remove the old domain vm = self.domains.pop(dom.name()) self.handler.tag_db.remove_sub_object(vm.name) logger.debug('Domain %s recreated', dom.name()) vm = VirtualMachine(dom, self) logger.info('Created domain %s', vm.name) self.domains[vm.name] = vm self.handler.tag_db.add_sub_object(vm.name, vm.tags.itervalues()) self.update_domain_count() elif event == 'Removed': vm_name = dom.name() self.vm_unregister(vm_name) logger.info('Removed domain %s', vm_name) elif event in ('Started', 'Suspended', 'Resumed', 'Stopped', 'Saved', 'Restored'): vm = self.domains.get(dom.name()) # sometimes libvirt sent a start event before a created event so be # careful if vm is not None: try: state = DOMAIN_STATES[dom.info()[0]] except libvirt.libvirtError as exc: # checks that domain was not previously removed # seems to happen only in libvirt 0.8.8 if 'Domain not found' in str(exc): self.vm_unregister(dom.name()) else: raise else: logger.info('Domain change state from %s to %s', vm.state, state) vm.state = state self.update_domain_count() def vm_unregister(self, name): """Unregister a VM from the cc-server and remove it from the index.""" try: vm = self.domains.pop(name) except KeyError: # domain already removed, see hypervisor/domains/vm_tags.py # sometimes libvirt send us the remove event too late # we still update storage and tag attributes pass else: self.handler.tag_db.remove_sub_object(vm.name) # update Storage pools in case VM had volumes that were deleted self.storage.update() self.update_domain_count() def update_domain_count(self): """Update domain state count tags.""" # update domain state counts for tag in ('nvm', 'vmpaused', 'vmstarted', 'vmstopped', 'cpualloc', 'cpurunning', 'memalloc', 'memrunning'): self.handler.tag_db['__main__'][tag].update_value() def vm_define(self, xml_desc): """Create a VM on the Hypervisor :param str xml_desc: XML description in libvirt format :return: VM name created """ try: return self.vir_con.defineXML(xml_desc).name() except libvirt.libvirtError: logger.exception('Error while creating domain') # reraise exception for the cc-server raise def _count_domain(self, filter=lambda d: True): count = 0 for dom in self.domains.itervalues(): if filter(dom): count += 1 return count @property def vm_started(self): """Number of VMs started.""" return self._count_domain(lambda d: d.state == 'running') @property def vm_stopped(self): """Number of VMs stopped.""" return self._count_domain(lambda d: d.state == 'stopped') @property def vm_paused(self): """Number of VMs paused.""" return self._count_domain(lambda d: d.state == 'paused') @property def vm_total(self): """Total number of VMs on the hypervisor.""" return self._count_domain()