Create RequestSpec Object

https://blueprints.launchpad.net/nova/+spec/request-spec-object

Add a structured, documented object that represents a specification for launching multiple instances in a cloud.

Problem description

The main interface into the scheduler, the select_destinations() method, accepts a request_spec parameter that is a nested dict. This nested dict is constructed in nova.scheduler.utils.build_request_spec(), however the structure of the request spec is not documented anywhere and the filters in the scheduler seem to take a laisse faire approach to querying the object during scheduling as well as modifying the request_spec object during loops of the nova.scheduler.host_manager.HostStateManager.get_filtered_hosts() method, which calls the filter object’s host_passes object, supplying a filter_properties parameter, which itself has a key called request_spec that contains the aforementioned nested dict.

This situation makes it very difficult to understand exactly what is going on in the scheduler, and cleaning up this parameter in the scheduler interface is a pre-requisite to making a properly-versioned and properly-documented interface in preparation for a split-out of the scheduler code.

Use Cases

This is a pure refactoring effort for cleaning up all the interfaces in between Nova and the scheduler so the scheduler could be split out by the next cycle.

Project Priority

This blueprint is part of a global effort around the ‘scheduler’ refactoring for helping it to be split out. This has been defined as the 3rd priority for this Kilo cycle.

Proposed change

A new class called RequestSpec will be created that models a request to launch multiple virtual machine instances. The first version of the RequestSpec object will simply be an objectified version of the current dictionary parameter. The scheduler will construct this RequestSpec object from the request_spec dictionary itself.

The existing nova.scheduler.utils.build_request_spec method will be removed in favor of a factory method on nova.objects.request_spec.RequestSpec that will construct a RequestSpec from the existing key/value pairs in the request_spec parameter supplied to select_destinations.

Alternatives

None.

Data model impact

This spec is not focusing on persisting the RequestSpec object but another blueprint (and a spec) will be proposed with this one as dependency for providing a save() method to the RequestSpec object which would allow it to be persisted in (probably) instance_extra DB table.

REST API impact

None.

Security impact

None.

Notifications impact

None.

Other end user impact

None.

Performance Impact

None.

Other deployer impact

None.

Developer impact

None, besides making the scheduler call interfaces gradually easier to read and understand.

Implementation

The request_spec dictionary is currently constructed by the nova-conductor when it calls the nova.scheduler.utils.build_request_spec() function, which looks like this:

def build_request_spec(ctxt, image, instances, instance_type=None):
   """Build a request_spec for the scheduler.

   The request_spec assumes that all instances to be scheduled are the same
   type.
   """
   instance = instances[0]
   if isinstance(instance, obj_base.NovaObject):
       instance = obj_base.obj_to_primitive(instance)

   if instance_type is None:
       instance_type = flavors.extract_flavor(instance)
   # NOTE(comstud): This is a bit ugly, but will get cleaned up when
   # we're passing an InstanceType internal object.
   extra_specs = db.flavor_extra_specs_get(ctxt, instance_type['flavorid'])
   instance_type['extra_specs'] = extra_specs
   request_spec = {
           'image': image or {},
           'instance_properties': instance,
           'instance_type': instance_type,
           'num_instances': len(instances),
           # NOTE(alaski): This should be removed as logic moves from the
           # scheduler to conductor.  Provides backwards compatibility now.
           'instance_uuids': [inst['uuid'] for inst in instances]}
   return jsonutils.to_primitive(request_spec)

A possible first version of a class interface for the RequestSpec class would look like this, in order to be as close to a straight conversion from the nested dict’s keys to object attribute notation:

class RequestSpec(base.NovaObject):

   """Models the request to launch one or more instances in the cloud."""

   VERSION = '1.0'

   fields = {
       'image': fields.DictOfStringsField(nullable=False),
       # instance_properties could eventually be deconstructed into component
       # parts
       'instance_properties': fields.ObjectField('Instance'),
       'instance_type': fields.ObjectField('Flavor', nullable=False),
       'num_instances': fields.IntegerField(default=1),
   }

As request_spec dictionary is having a deprecated instance_uuids field, we will not provide this field into the RequestSpec object and instead bump the RPC API version and remove it from the dictionary before as a work item.

This blueprint does not change the select_destinations scheduler RPC API method, so we would construct a nova.objects.request_spec.RequestSpec object on the nova-scheduler side, from the request_spec dictionary key in the FilterScheduler._schedule() method, like so:

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()
    request_spec = objects.RequestSpec.from_dict(request_spec)
    instance_type = request_spec.instance_type

    update_group_hosts = self._setup_instance_group(context,
            filter_properties)

    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 = []
    if instance_uuids:
        num_instances = len(instance_uuids)
    else:
        num_instances = request_spec.num_instances
    for num in xrange(num_instances):
        # Filter local hosts based on requirements ...
        hosts = self.host_manager.get_filtered_hosts(hosts,
                filter_properties, index=num)

The final steps of implementing this blueprint would be to change all of the scheduler filters to access the original properties via object notation instead of dict-notation.

As said above in the data model impact section, this blueprint is not targeting to persist this object at the moment. We are also not yet removing the filter_properties dictionary in this blueprint.

Assignee(s)

Primary assignee:

bauzas

Other contributors:

None

Work Items

  • Remove instance_uuids field from request_spec and bump a new version for the Scheduler RPC API

  • Add request spec class to nova/scheduler/request_spec.py w/ unit tests

  • Add a factory classmethod on nova.scheduler.RequestSpec that constructs a RequestSpec object from the existing set of instance type extra_specs, scheduler_hints, flavor and image objects that are supplied to the nova.scheduler.utils.build_request_spec function.

  • Modify FilterScheduler._schedule() to construct a RequestSpec object from the supplied nested request_spec dictionary

  • Convert all filter classes to operate against the RequestSpec object instead the nested request_spec dictionary.

  • Add developer reference documentation for what the request spec models.

Dependencies

None.

Testing

New unit tests for the request spec objects will be added. The existing unit tests of the scheduler filters will be modified to access the RequestSpec object in the filter_properties dictionary.

Documentation Impact

Update any developer reference material that might be referencing the old dictionary accesses.

References

This blueprint is part of an overall effort to clean up, version, and stabilize the interfaces between the nova-api, nova-scheduler, nova-conductor and nova-compute daemons that involve scheduling and resource decisions.

See https://wiki.openstack.org/wiki/Gantt/kilo#Tasks for the list of all blueprints targeted for Kilo.