示例#1
0
 def __init__(self, *args, **kwargs):
     super(FilterScheduler, self).__init__(*args, **kwargs)
     self.options = scheduler_options.SchedulerOptions()
     self.compute_rpcapi = compute_rpcapi.ComputeAPI()
     self.notifier = rpc.get_notifier('scheduler')
     self.paxes_compute_rpcapi = PowerVCComputeRPCAPI()
示例#2
0
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.compute_rpcapi = compute_rpcapi.ComputeAPI()
        self.notifier = rpc.get_notifier('scheduler')
        self.paxes_compute_rpcapi = PowerVCComputeRPCAPI()

    def scheduler_image_create(self, context,
                              image_meta,
                              image_data):
        
        LOG.info(_("Attempting to build %(image_id)s image(s) "
                    "data: %(location)s"),
                  {'image_id': image_data.get('image_id'),
                   'location': image_data.get('location')})

        image_id = image_data.get('image_id', None)
        if image_id is None:
            raise NotFoundImageID({'image_id' : image_id})
        storage_type = image_data.get('storage_type', 'san')
        #filter_properties = {'instance_type' : {'memory_mb' : 1}}
        filter_properties = {}
        scheduler_hints = {'query' : '["=","$stats.host_storage_type", "%s"]' %storage_type}
        filter_properties['scheduler_hints'] = scheduler_hints
        weighed_hosts = self._image_schedule(context, filter_properties)
        try:
            try:
                weighed_host = weighed_hosts.pop(0)
                LOG.info(_("Choosing host %(weighed_host)s "
                            "for instance %(image_id)s"),
                          {'weighed_host': weighed_host,
                           'image_id': image_id})
            except IndexError:
                raise exception.NoValidHost(reason="")

            self.paxes_compute_rpcapi.image_create(context,
                                            image_meta=image_meta,
                                            image_data=image_data,
                                            host=weighed_host.obj.host)
        except Exception as ex:
            # NOTE(vish): we don't reraise the exception here to make sure
            #             that all instances in the request get set to
            #             error properly
            raise CreateImageFailure(image_id=image_id)
        # scrub retry host list in case we're scheduling multiple
        # instances:
        retry = filter_properties.get('retry', {})
        retry['hosts'] = []
        self.notifier.info(context, 'scheduler.run_instance.end', filter_properties)
    
    def _image_schedule(self, context, filter_properties    ):
        elevated = context.elevated()
        hosts = self._get_all_host_states(elevated)
        # Filter local hosts based on requirements ...
        hosts = self.host_manager.get_filtered_hosts(hosts,
                                                     filter_properties, 
                                                     index=0)
        if not hosts:
            # Can't get any more locally.
            return 

        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})
        return weighed_hosts

    def schedule_run_instance(self, context, request_spec,
                              admin_password, injected_files,
                              requested_networks, is_first_time,
                              filter_properties, legacy_bdm_in_spec):
        """This method is called from nova.compute.api to provision
        an instance.  We first create a build plan (a list of WeightedHosts)
        and then provision.

        Returns a list of the instances created.
        """
        payload = dict(request_spec=request_spec)
        self.notifier.info(context, 'scheduler.run_instance.start', payload)

        instance_uuids = request_spec.get('instance_uuids')
        LOG.info(_("Attempting to build %(num_instances)d instance(s) "
                    "uuids: %(instance_uuids)s"),
                  {'num_instances': len(instance_uuids),
                   'instance_uuids': instance_uuids})
        LOG.debug(_("Request Spec: %s") % request_spec)

        weighed_hosts = self._schedule(context, request_spec,
                                       filter_properties, instance_uuids)

        # NOTE: Pop instance_uuids as individual creates do not need the
        # set of uuids. Do not pop before here as the upper exception
        # handler fo NoValidHost needs the uuid to set error state
        instance_uuids = request_spec.pop('instance_uuids')

        # NOTE(comstud): Make sure we do not pass this through.  It
        # contains an instance of RpcContext that cannot be serialized.
        filter_properties.pop('context', None)

        for num, instance_uuid in enumerate(instance_uuids):
            request_spec['instance_properties']['launch_index'] = num

            try:
                try:
                    weighed_host = weighed_hosts.pop(0)
                    LOG.info(_("Choosing host %(weighed_host)s "
                                "for instance %(instance_uuid)s"),
                              {'weighed_host': weighed_host,
                               'instance_uuid': instance_uuid})
                except IndexError:
                    raise exception.NoValidHost(reason="")

                self._provision_resource(context, weighed_host,
                                         request_spec,
                                         filter_properties,
                                         requested_networks,
                                         injected_files, admin_password,
                                         is_first_time,
                                         instance_uuid=instance_uuid,
                                         legacy_bdm_in_spec=legacy_bdm_in_spec)
            except Exception as ex:
                # NOTE(vish): we don't reraise the exception here to make sure
                #             that all instances in the request get set to
                #             error properly
                driver.handle_schedule_error(context, ex, instance_uuid,
                                             request_spec)
            # scrub retry host list in case we're scheduling multiple
            # instances:
            retry = filter_properties.get('retry', {})
            retry['hosts'] = []

        self.notifier.info(context, 'scheduler.run_instance.end', payload)

    def select_destinations(self, context, request_spec, filter_properties):
        """Selects a filtered set of hosts and nodes."""
        num_instances = request_spec['num_instances']
        instance_uuids = request_spec.get('instance_uuids')
        selected_hosts = self._schedule(context, request_spec,
                                        filter_properties, instance_uuids)

        # Couldn't fulfill the request_spec
        if len(selected_hosts) < num_instances:
            raise exception.NoValidHost(reason='')

        dests = [dict(host=host.obj.host, nodename=host.obj.nodename,
                      limits=host.obj.limits) for host in selected_hosts]
        return dests

    def _provision_resource(self, context, weighed_host, request_spec,
            filter_properties, requested_networks, injected_files,
            admin_password, is_first_time, instance_uuid=None,
            legacy_bdm_in_spec=True):
        """Create the requested resource in this Zone."""
        # NOTE(vish): add our current instance back into the request spec
        request_spec['instance_uuids'] = [instance_uuid]
        payload = dict(request_spec=request_spec,
                       weighted_host=weighed_host.to_dict(),
                       instance_id=instance_uuid)
        self.notifier.info(context,
                           'scheduler.run_instance.scheduled', payload)

        # Update the metadata if necessary
        scheduler_hints = filter_properties.get('scheduler_hints') or {}
        try:
            updated_instance = driver.instance_update_db(context,
                                                         instance_uuid)
        except exception.InstanceNotFound:
            LOG.warning(_("Instance disappeared during scheduling"),
                        context=context, instance_uuid=instance_uuid)

        else:
            scheduler_utils.populate_filter_properties(filter_properties,
                    weighed_host.obj)

            self.compute_rpcapi.run_instance(context,
                    instance=updated_instance,
                    host=weighed_host.obj.host,
                    request_spec=request_spec,
                    filter_properties=filter_properties,
                    requested_networks=requested_networks,
                    injected_files=injected_files,
                    admin_password=admin_password, is_first_time=is_first_time,
                    node=weighed_host.obj.nodename,
                    legacy_bdm_in_spec=legacy_bdm_in_spec)

    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
        pci_requests = pci_request.get_pci_requests_from_flavor(
            request_spec.get('instance_type') or {})
        if pci_requests:
            filter_properties['pci_requests'] = pci_requests

    def _max_attempts(self):
        max_attempts = CONF.scheduler_max_attempts
        if max_attempts < 1:
            raise exception.NovaException(_("Invalid value for "
                "'scheduler_max_attempts', must be >= 1"))
        return max_attempts

    def _log_compute_error(self, instance_uuid, retry):
        """If the request contained an exception from a previous compute
        build/resize operation, log it to aid debugging
        """
        exc = retry.pop('exc', None)  # string-ified exception from compute
        if not exc:
            return  # no exception info from a previous attempt, skip

        hosts = retry.get('hosts', None)
        if not hosts:
            return  # no previously attempted hosts, skip

        last_host, last_node = hosts[-1]
        LOG.error(_('Error from last host: %(last_host)s (node %(last_node)s):'
                    ' %(exc)s'),
                  {'last_host': last_host,
                   'last_node': last_node,
                   'exc': exc},
                  instance_uuid=instance_uuid)

    def _populate_retry(self, filter_properties, instance_properties):
        """Populate filter properties with history of retries for this
        request. If maximum retries is exceeded, raise NoValidHost.
        """
        max_attempts = self._max_attempts()
        force_hosts = filter_properties.get('force_hosts', [])
        force_nodes = filter_properties.get('force_nodes', [])

        if max_attempts == 1 or force_hosts or force_nodes:
            # re-scheduling is disabled.
            return

        retry = filter_properties.pop('retry', {})
        # retry is enabled, update attempt count:
        if retry:
            retry['num_attempts'] += 1
        else:
            retry = {
                'num_attempts': 1,
                'hosts': []  # list of compute hosts tried
            }
        filter_properties['retry'] = retry

        instance_uuid = instance_properties.get('uuid')
        self._log_compute_error(instance_uuid, retry)

        if retry['num_attempts'] > max_attempts:
            msg = (_('Exceeded max scheduling attempts %(max_attempts)d for '
                     'instance %(instance_uuid)s')
                   % {'max_attempts': max_attempts,
                      'instance_uuid': instance_uuid})
            raise exception.NoValidHost(reason=msg)

    @staticmethod
    def _setup_instance_group(context, filter_properties):
        update_group_hosts = False
        scheduler_hints = filter_properties.get('scheduler_hints') or {}
        group_uuid = scheduler_hints.get('group', None)
        if group_uuid:
            group = instance_group_obj.InstanceGroup.get_by_uuid(context,
                    group_uuid)
            policies = set(('anti-affinity', 'affinity'))
            if any((policy in policies) for policy in group.policies):
                update_group_hosts = True
                filter_properties.setdefault('group_hosts', set())
                user_hosts = set(filter_properties['group_hosts'])
                group_hosts = set(group.get_hosts(context))
                filter_properties['group_hosts'] = user_hosts | group_hosts
                filter_properties['group_policies'] = group.policies
        return update_group_hosts

    def _schedule(self, context, request_spec, filter_properties,
                  instance_uuids=None):
        """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 = self._setup_instance_group(context,
                filter_properties)

        config_options = self._get_configuration_options()

        # check retry policy.  Rather ugly use of instance_uuids[0]...
        # but if we've exceeded max retries... then we really only
        # have a single instance.
        properties = instance_properties.copy()
        if instance_uuids:
            properties['uuid'] = instance_uuids[0]
        self._populate_retry(filter_properties, properties)

        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 = []
        if instance_uuids:
            num_instances = len(instance_uuids)
        else:
            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])
            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:
                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)