#!/usr/bin/env python #coding=utf8 ''' This module contains the hypervisor destination election stuff. ''' from __future__ import absolute_import from copy import copy from ccserver.exceptions import (UnknownElectionAlgo, UnknownElectionType, ElectionError) def tags(*args): ''' Decorator used to declare tags used by a filter. ''' def decorator(func): func.__tags__ = set(args) return func return decorator class Elector(object): # Filtering function for destination hypervisors: FILTERS = { 'cold': (('is_hv', 'filter r=hv'), ('not_source_hv', 'filter source hv'), ('is_connected', 'filter connected hv'), ('vm_htype_eq_hv', 'filter bad hv types'), ('has_rights', 'filter rights'), ('has_alloc', 'filter allocatable hv'), ('duplicate_name', 'filter vm duplicate names'), ('enough_disk', 'filter hv with not enough disk'),), } # Available algos for each types: ALGO_BY_TYPES = {'cold': ('fair', )} def __init__(self, server, query_vm, query_dest, login): # The server instance for this election: self._server = server # The TQL query to select vm: self._query_vm = query_vm # The TQL query to select destination hypervisor: self._query_dest = query_dest # The login of the initiator of the election: self._login = login def election(self, mtype, algo): ''' Generate a new migration plan for this election. You must specify the migration type and the distribution algoritm. :param mtype: the migration type :param algo: the distribution algoritm ''' # Check the choosen election method: if mtype not in self.ALGO_BY_TYPES: raise UnknownElectionType('%r is unknown migration type' % mtype) else: if algo not in self.ALGO_BY_TYPES[mtype]: raise UnknownElectionAlgo('%r is unknown migration algo' % algo) else: func = '_algo_%s' % algo if not hasattr(self, func): raise UnknownElectionAlgo('%r not found' % func) else: distribute = getattr(self, func) # Get the destination hypervisor candidates: candidates = self._get_candidates(self.FILTERS[mtype]) # Distributes VMs to each candidate: migration_plan = distribute(candidates) # Return the migration plan: return migration_plan def _get_candidates(self, filters): # Get all the tags needed for hypervisors and construct the final # filter list: filterfuncs = [] hv_tags = set() for name, desc in filters: filterfunc = getattr(self, '_filter_%s' % name) filterfuncs.append((filterfunc, desc)) hv_tags |= getattr(filterfunc, '__tags__', set()) # Get the selected vms and hvs: vms = self._server.list(self._query_vm, show=('*',)) hvs = self._server.list(self._query_dest, show=hv_tags) candidates = [] # Filters the candidates: for vm in vms: if vm['r'] != 'vm': continue tql = 'id=%s' % vm['id'] if not self._server.check(self._login, 'coldmigrate', tql): continue vm_dest = copy(hvs) for func, desc in filterfuncs: vm_dest = func(vm, vm_dest) candidates.append((vm, vm_dest)) return candidates ##### ##### Distribution algorithm methods: ##### def _algo_fair(self, candidates): migration_plan = [] # Sort vm by number of destination hv: candidates = sorted(candidates, key=lambda x: len(x[1])) hv_alloc = {} for vm, hvs in candidates: if not hvs: raise ElectionError('No destination found for %r vm' % vm['id']) else: # Try to take an hypervisor that is not already in the plan: for hv in hvs: if hv['id'] not in hv_alloc: break else: # If all candidates for this VM are already in the # migration plan, we take the less allocated: hv = min(hvs, key=lambda x: hv_alloc[x['id']]) migration_plan.append({'sid': vm['id'], 'did': hv['id'], 'type': 'cold'}) hv_alloc[hv['id']] = hv_alloc.get(hv['id'], 0) + 1 return migration_plan ##### ##### Filtering methods: ##### @tags('r') def _filter_is_hv(self, vm, hvs): returned = [] for hv in hvs: if hv.get('r') == 'hv': returned.append(hv) return returned @tags('con') def _filter_is_connected(self, vm, hvs): returned = [] for hv in hvs: if hv.get('con'): returned.append(hv) return returned @tags('htype') def _filter_vm_htype_eq_hv(self, vm, hvs): returned = [] vm_htype = vm.get('htype') for hv in hvs: if hv.get('htype') == vm_htype: returned.append(hv) return returned @tags('id') def _filter_not_source_hv(self, vm, hvs): returned = [] hv_id, _, _ = vm['id'].partition('.') for hv in hvs: if hv['id'] != hv_id: returned.append(hv) return returned def _filter_has_rights(self, vm, hvs): returned = [] for hv in hvs: tql = 'id=%s' % hv['id'] if self._server.check(self._login, 'coldmigrate_dest', tql): returned.append(hv) return returned @tags('alloc') def _filter_has_alloc(self, vm, hvs): returned = [] for hv in hvs: if hv.get('alloc', False): returned.append(hv) return returned def _filter_duplicate_name(self, vm, hvs): returned = [] hv_id, _, vm_name = vm['id'].partition('.') for hv in hvs: vms = self._server.list('id:%s.*$h' % hv['id']) duplicate = False for vm in vms: if vm.get('h') == vm_name: duplicate = True break if not duplicate: returned.append(hv) return returned @tags('memfree') def _filter_enough_ram(self, vm, hvs): returned = [] for hv in hvs: if int(hv.get('memfree', 0)) >= int(vm.get('mem', 0)): returned.append(hv) return returned @tags('cpu') def _filter_enough_core(self, vm, hvs): returned = [] for hv in hvs: # Calculate the total number of vcpu used by VMs: vms = self.manager.server.list('id:%s.*$cpu' % hv['id']) count = 0 for vm in vms: count += int(vm.get('cpu', 0)) if int(hv.get('cpu', 0)) >= count + int(self.vm.get('cpu', 0)): returned.append(hv) return returned @tags('*') def _filter_enough_disk(self, vm, hvs): returned = [] return hvs # Calculate the size needed for each pools: pools = {} for disk in vm['disk'].split(): size = vm['disk%s_size' % disk] pool = vm['disk%s_pool' % disk] vol = vm['disk%s_vol' % disk] dpool = pools.get(pool, {'size': 0, 'vols': set()}) dpool['size'] += int(size) dpool['vols'].add(vol) pools[pool] = dpool # Check for each HV if it match: for hv in hvs: good = True for pool, prop in pools.iteritems(): free = int(hv.get('sto%s_free' % pool, 0)) vols = set(hv.get('sto%s_vol' % pool, '').split()) if free < prop['size'] or prop['vols'] & vols: good = False if good: returned.append(hv) return returned def _filter_not_locked(self, vm, hvs): if len(hvs) == 1: return hvs returned = [] for hv in hvs: lock = self.manager.server.get_connection(hv['id']).lock if not lock.locked(): returned.append(hv) if len(returned) == 0: return hvs return returned def _filter_biggest_id(self, hvs): return sorted(hvs, key=lambda x: x['id'])[-1]