# Copyright 2014 Budapest University of Technology and Economics (BME IK) # # This file is part of CIRCLE Cloud. # # CIRCLE is free software: you can redistribute it and/or modify it under # the terms of the GNU General Public License as published by the Free # Software Foundation, either version 3 of the License, or (at your option) # any later version. # # CIRCLE 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 General Public License for more # details. # # You should have received a copy of the GNU General Public License along # with CIRCLE. If not, see <http://www.gnu.org/licenses/>. from logging import getLogger from django.db.models import Sum from django.utils.translation import ugettext_noop from common.models import HumanReadableException logger = getLogger(__name__) class SchedulerError(HumanReadableException): admin_message = None def __init__(self, params=None, level=None, **kwargs): kwargs.update(params or {}) super(SchedulerError, self).__init__( level, self.message, self.admin_message or self.message, kwargs) class NotEnoughMemoryException(SchedulerError): message = ugettext_noop( "The resources required for launching the virtual machine are not " "available currently. Please try again later.") admin_message = ugettext_noop( "The required free memory for launching the virtual machine is not " "available on any usable node currently. Please try again later.") class TraitsUnsatisfiableException(SchedulerError): message = ugettext_noop( "No node can satisfy the required traits of the " "new virtual machine currently.") def select_node(instance, nodes): ''' Select a node for hosting an instance based on its requirements. ''' # check required traits nodes = [n for n in nodes if n.enabled and n.online and has_traits(instance.req_traits.all(), n)] if not nodes: logger.warning('select_node: no usable node for %s', unicode(instance)) raise TraitsUnsatisfiableException() # check required RAM nodes = [n for n in nodes if has_enough_ram(instance.ram_size, n)] if not nodes: logger.warning('select_node: no enough RAM for %s', unicode(instance)) raise NotEnoughMemoryException() # sort nodes first by processor usage, then priority nodes.sort(key=lambda n: n.priority, reverse=True) nodes.sort(key=free_cpu_time, reverse=True) result = nodes[0] logger.info('select_node: %s for %s', unicode(result), unicode(instance)) return result def has_traits(traits, node): """True, if the node has all specified traits; otherwise, false. """ traits = set(traits) return traits.issubset(node.traits.all()) def has_enough_ram(ram_size, node): """True, if the node has enough memory to accomodate a guest requiring ram_size mebibytes of memory; otherwise, false. """ ram_size = ram_size * 1024 * 1024 try: total = node.ram_size used = node.byte_ram_usage unused = total - used overcommit = node.ram_size_with_overcommit reserved = (node.instance_set.aggregate( r=Sum('ram_size'))['r'] or 0) * 1024 * 1024 free = overcommit - reserved retval = ram_size < unused and ram_size < free logger.debug('has_enough_ram(%d, %s)=%s (total=%s unused=%s' ' overcommit=%s free=%s free_ok=%s overcommit_ok=%s)', ram_size, node, retval, total, unused, overcommit, free, ram_size < unused, ram_size < free) return retval except TypeError as e: logger.exception('Got incorrect monitoring data for node %s. %s', unicode(node), unicode(e)) return False def free_cpu_time(node): """Get an indicator number for idle processor time on the node. Higher values indicate more idle time. """ try: activity = node.cpu_usage / 100 inactivity = 1 - activity cores = node.num_cores return cores * inactivity except TypeError as e: logger.warning('Got incorrect monitoring data for node %s. %s', unicode(node), unicode(e)) return False # monitoring data is incorrect