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()
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)