# Copyright (c) 2011 OpenStack Foundation # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. """ The FilterScheduler is for creating instances locally. You can customize this scheduler by specifying your own Host Filters and Weighing Functions. """ import random from oslo_config import cfg from oslo_log import log as logging from six.moves import range from nova import exception from nova.i18n import _ from nova import rpc from nova.scheduler import driver from nova.scheduler import scheduler_options CONF = cfg.CONF LOG = logging.getLogger(__name__) filter_scheduler_opts = [ cfg.IntOpt('scheduler_host_subset_size', default=1, help='New instances will be scheduled on a host chosen ' 'randomly from a subset of the N best hosts. This ' 'property defines the subset size that a host is ' 'chosen from. A value of 1 chooses the ' 'first host returned by the weighing functions. ' 'This value must be at least 1. Any value less than 1 ' 'will be ignored, and 1 will be used instead') ] CONF.register_opts(filter_scheduler_opts) class FilterScheduler(driver.Scheduler): """Scheduler that can be used for filtering and weighing.""" def __init__(self, *args, **kwargs): super(FilterScheduler, self).__init__(*args, **kwargs) self.options = scheduler_options.SchedulerOptions() self.notifier = rpc.get_notifier('scheduler') def select_destinations(self, context, request_spec, filter_properties): """Selects a filtered set of hosts and nodes.""" self.notifier.info(context, 'scheduler.select_destinations.start', dict(request_spec=request_spec)) num_instances = request_spec['num_instances'] selected_hosts = self._schedule(context, request_spec, filter_properties) # Couldn't fulfill the request_spec if len(selected_hosts) < num_instances: # NOTE(Rui Chen): If multiple creates failed, set the updated time # of selected HostState to None so that these HostStates are # refreshed according to database in next schedule, and release # the resource consumed by instance in the process of selecting # host. for host in selected_hosts: host.obj.updated = None # Log the details but don't put those into the reason since # we don't want to give away too much information about our # actual environment. LOG.debug('There are %(hosts)d hosts available but ' '%(num_instances)d instances requested to build.', {'hosts': len(selected_hosts), 'num_instances': num_instances}) reason = _('There are not enough hosts available.') raise exception.NoValidHost(reason=reason) dests = [dict(host=host.obj.host, nodename=host.obj.nodename, limits=host.obj.limits) for host in selected_hosts] self.notifier.info(context, 'scheduler.select_destinations.end', dict(request_spec=request_spec)) return dests def _get_configuration_options(self): """Fetch options dictionary. Broken out for testing.""" return self.options.get_configuration() def _schedule(self, context, request_spec, filter_properties): """Returns a list of hosts that meet the required specs, ordered by their fitness. """ elevated = context.elevated() instance_properties = request_spec['instance_properties'] instance_type = request_spec.get("instance_type", None) update_group_hosts = filter_properties.get('group_updated', False) config_options = self._get_configuration_options() filter_properties.update({'context': context, 'request_spec': request_spec, 'config_options': config_options, 'instance_type': instance_type}) # Find our local list of acceptable hosts by repeatedly # filtering and weighing our options. Each time we choose a # host, we virtually consume resources on it so subsequent # selections can adjust accordingly. # Note: remember, we are using an iterator here. So only # traverse this list once. This can bite you if the hosts # are being scanned in a filter or weighing function. hosts = self._get_all_host_states(elevated) selected_hosts = [] num_instances = request_spec.get('num_instances', 1) for num in range(num_instances): # Filter local hosts based on requirements ... hosts = self.host_manager.get_filtered_hosts(hosts, filter_properties, index=num) if not hosts: # Can't get any more locally. break LOG.debug("Filtered %(hosts)s", {'hosts': hosts}) weighed_hosts = self.host_manager.get_weighed_hosts(hosts, filter_properties) LOG.debug("Weighed %(hosts)s", {'hosts': weighed_hosts}) scheduler_host_subset_size = CONF.scheduler_host_subset_size if scheduler_host_subset_size > len(weighed_hosts): scheduler_host_subset_size = len(weighed_hosts) if scheduler_host_subset_size < 1: scheduler_host_subset_size = 1 chosen_host = random.choice( weighed_hosts[0:scheduler_host_subset_size]) LOG.debug("Selected host: %(host)s", {'host': chosen_host}) selected_hosts.append(chosen_host) # Now consume the resources so the filter/weights # will change for the next instance. chosen_host.obj.consume_from_instance(instance_properties) if update_group_hosts is True: # NOTE(sbauza): Group details are serialized into a list now # that they are populated by the conductor, we need to # deserialize them if isinstance(filter_properties['group_hosts'], list): filter_properties['group_hosts'] = set( filter_properties['group_hosts']) filter_properties['group_hosts'].add(chosen_host.obj.host) return selected_hosts def _get_all_host_states(self, context): """Template method, so a subclass can implement caching.""" return self.host_manager.get_all_host_states(context)