text: # 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 patron import exception
  from patron.i18n import _
  from patron import rpc
  from patron.scheduler import driver
  from patron.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:
              # 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 populate_filter_properties(self, request_spec, filter_properties):
          """Stuff things into filter_properties.  Can be overridden in a
          subclass to add more data.
          """
          # Save useful information from the request spec for filter processing:
          project_id = request_spec['instance_properties']['project_id']
          os_type = request_spec['instance_properties']['os_type']
          filter_properties['project_id'] = project_id
          filter_properties['os_type'] = os_type
  
      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})
  
          self.populate_filter_properties(request_spec,
                                          filter_properties)
  
          # 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 xrange(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)
  