Skip to content

Commit

Permalink
Merge "Make scheduler filters more pluggable"
Browse files Browse the repository at this point in the history
  • Loading branch information
Jenkins authored and openstack-gerrit committed Feb 28, 2012
2 parents 0b99bc2 + 3f42e11 commit e5ad06a
Show file tree
Hide file tree
Showing 14 changed files with 194 additions and 135 deletions.
27 changes: 21 additions & 6 deletions doc/source/devref/distributed_scheduler.rst
Expand Up @@ -55,15 +55,28 @@ The filtering (excluding compute nodes incapable of fulfilling the request) and
Host Filter
-----------

As we mentioned earlier, filtering hosts is a very deployment-specific process. Service Providers may have a different set of criteria for filtering Compute nodes than a University. To faciliate this the `nova.scheduler.filters` module supports a variety of filtering strategies as well as an easy means for plugging in your own algorithms.
As we mentioned earlier, filtering hosts is a very deployment-specific process. Service Providers may have a different set of criteria for filtering Compute nodes than a University. To facilitate this, the `DistributedScheduler` supports a variety of filtering strategies as well as an easy means for plugging in your own algorithms. Specifying filters involves 2 settings. One makes filters available for use. The second specifies which filters to use by default (out of the filters available). The reason for this second option is that there may be support to allow end-users to specify specific filters during a build at some point in the future.

The filter used is determined by the `--default_host_filters` flag, which points to a Python Class. By default this flag is set to `[AllHostsFilter]` which simply returns all available hosts. But there are others:
Making filters available:

* `ComputeFilter` provides host filtering based on the memory and disk size specified in the `InstanceType` record passed into `run_instance`.
Filters are made available to the scheduler via the `--scheduler_available_filters` setting. This setting can be specified more than once and should contain lists of filter class names (with full paths) to make available. Specifying 'nova.scheduler.filters.standard_filters' will cause all standard filters under 'nova.scheduler.filters' to be made available. That is the default setting. Additionally, you can specify your own classes to be made available. For example, 'myfilter.MyFilterClass' can be specified. Now that you've configured which filters are available, you should set which ones you actually want to use by default.

Setting the default filtering classes:

The default filters to use are set via the `--scheduler_default_filters` setting. This setting should contain a list of class names. You should not specify the full paths with these class names. By default this flag is set to `['AvailabilityZoneFilter', 'RamFilter', 'ComputeFilter']`. Below is a list of standard filter classes:

* `AllHostsFilter` includes all hosts (essentially is a No-op filter)
* `AvailabilityZoneFilter` provides host filtering based on availability_zone
* `ComputeFilter` provides host filtering based on `InstanceType` extra_specs, comparing against host capability announcements
* `CoreFilter` provides host filtering based on number of cpu cores
* `DifferentHostFilter` provides host filtering based on scheduler_hint's 'different_host' value. With the scheduler_hints extension, this allows one to put a new instance on a different host from another instance
* `IsolatedHostsFilter` provides host filtering based on the 'isolated_hosts' and 'isolated_images' flags/settings.
* `JSONFilter` filters hosts based on simple JSON expression grammar. Using a LISP-like JSON structure the caller can request instances based on criteria well beyond what `ComputeFilter` specifies. See `nova.tests.scheduler.test_host_filters` for examples.
* `RamFilter` provides host filtering based on the memory needed vs memory free
* `SameHostFilter` provides host filtering based on scheduler_hint's 'same_host' value. With the scheduler_hints extension, this allows one to put a new instance on the same host as another instance
* `SimpleCIDRAffinityFilter` provides host filtering based on scheduler_hint's 'build_near_host_ip' value. With the scheduler_hints extension, this allows one to put a new instance on a host within the same IP block.

To create your own `HostFilter` the user simply has to derive from `nova.scheduler.filters.AbstractHostFilter` and implement one method: `host_passes`. This method accepts a `HostState` instance describing a host as well as a `filter_properties` dictionary. Host capabilities can be found in `HostState`.capabilities and other properites can be found in `filter_properties` like `instance_type`, etc. Your method should return True if it passes the filter.
To create your own `HostFilter` the user simply has to derive from `nova.scheduler.filters.BaseHostFilter` and implement one method: `host_passes`. This method accepts a `HostState` instance describing a host as well as a `filter_properties` dictionary. Host capabilities can be found in `HostState`.capabilities and other properites can be found in `filter_properties` like `instance_type`, etc. Your method should return True if it passes the filter.

Flags
-----
Expand All @@ -73,10 +86,12 @@ Here are some of the main flags you should set in your `nova.conf` file:
::

--scheduler_driver=nova.scheduler.distributed_scheduler.DistributedScheduler
--default_host_filters=nova.scheduler.filters.AllHostsFilter
--scheduler_available_filters=nova.scheduler.filters.standard_filters
# --scheduler_available_filters=myfilter.MyOwnFilter
--scheduler_default_filters=RamFilter,ComputeFilter,MyOwnFilter

`scheduler_driver` is the real workhorse of the operation. For Distributed Scheduler, you need to specify a class derived from `nova.scheduler.distributed_scheduler.DistributedScheduler`.
`default_host_filter` is the host filter to be used for filtering candidate Compute nodes.
`scheduler_default_filters` are the host filters to be used for filtering candidate Compute nodes.

Some optional flags which are handy for debugging are:

Expand Down
96 changes: 70 additions & 26 deletions nova/scheduler/filters/__init__.py
Expand Up @@ -14,30 +14,74 @@
# under the License.

"""
There are three filters included: AllHosts, InstanceType & JSON.
AllHosts just returns the full, unfiltered list of hosts.
InstanceType is a hard coded matching mechanism based on flavor criteria.
JSON is an ad-hoc filter grammar.
Why JSON? The requests for instances may come in through the
REST interface from a user or a parent Zone.
Currently InstanceTypes are used for specifing the type of instance desired.
Specific Nova users have noted a need for a more expressive way of specifying
instance requirements. Since we don't want to get into building full DSL,
this filter is a simple form as an example of how this could be done.
In reality, most consumers will use the more rigid filters such as the
InstanceType filter.
Scheduler host filters
"""
from nova.scheduler.filters.abstract_filter import AbstractHostFilter
from nova.scheduler.filters.affinity_filter import DifferentHostFilter
from nova.scheduler.filters.affinity_filter import SameHostFilter
from nova.scheduler.filters.affinity_filter import SimpleCIDRAffinityFilter
from nova.scheduler.filters.all_hosts_filter import AllHostsFilter
from nova.scheduler.filters.availability_zone_filter \
import AvailabilityZoneFilter
from nova.scheduler.filters.isolated_hosts_filter import IsolatedHostsFilter
from nova.scheduler.filters.compute_filter import ComputeFilter
from nova.scheduler.filters.core_filter import CoreFilter
from nova.scheduler.filters.json_filter import JsonFilter
from nova.scheduler.filters.ram_filter import RamFilter

import os
import types

from nova import exception
from nova import utils


class BaseHostFilter(object):
"""Base class for host filters."""

def host_passes(self, host_state, filter_properties):
raise NotImplemented()

def _full_name(self):
"""module.classname of the filter."""
return "%s.%s" % (self.__module__, self.__class__.__name__)


def _is_filter_class(cls):
"""Return whether a class is a valid Host Filter class."""
return type(cls) is types.TypeType and issubclass(cls, BaseHostFilter)


def _get_filter_classes_from_module(module_name):
"""Get all filter classes from a module."""
classes = []
module = utils.import_object(module_name)
for obj_name in dir(module):
itm = getattr(module, obj_name)
if _is_filter_class(itm):
classes.append(itm)
return classes


def standard_filters():
"""Return a list of filter classes found in this directory."""
classes = []
filters_dir = __path__[0]
for dirpath, dirnames, filenames in os.walk(filters_dir):
relpath = os.path.relpath(dirpath, filters_dir)
if relpath == '.':
relpkg = ''
else:
relpkg = '.%s' % '.'.join(relpath.split(os.sep))
for fname in filenames:
root, ext = os.path.splitext(fname)
if ext != '.py' or root == '__init__':
continue
module_name = "%s%s.%s" % (__package__, relpkg, root)
mod_classes = _get_filter_classes_from_module(module_name)
classes.extend(mod_classes)
return classes


def get_filter_classes(filter_class_names):
"""Get filter classes from class names."""
classes = []
for cls_name in filter_class_names:
obj = utils.import_class(cls_name)
if _is_filter_class(obj):
classes.append(obj)
elif type(obj) is types.FunctionType:
# Get list of classes from a function
classes.extend(obj())
else:
raise exception.ClassNotFound(class_name=cls_name,
exception='Not a valid scheduler filter')
return classes
25 changes: 0 additions & 25 deletions nova/scheduler/filters/abstract_filter.py

This file was deleted.

4 changes: 2 additions & 2 deletions nova/scheduler/filters/affinity_filter.py
Expand Up @@ -15,14 +15,14 @@
# limitations under the License.


import abstract_filter
import netaddr

from nova.compute import api as compute
from nova import flags
from nova.scheduler import filters


class AffinityFilter(abstract_filter.AbstractHostFilter):
class AffinityFilter(filters.BaseHostFilter):
def __init__(self):
self.compute_api = compute.API()

Expand Down
4 changes: 2 additions & 2 deletions nova/scheduler/filters/all_hosts_filter.py
Expand Up @@ -14,10 +14,10 @@
# under the License.


import abstract_filter
from nova.scheduler import filters


class AllHostsFilter(abstract_filter.AbstractHostFilter):
class AllHostsFilter(filters.BaseHostFilter):
"""NOP host filter. Returns all hosts."""

def host_passes(self, host_state, filter_properties):
Expand Down
4 changes: 2 additions & 2 deletions nova/scheduler/filters/availability_zone_filter.py
Expand Up @@ -14,10 +14,10 @@
# under the License.


import abstract_filter
from nova.scheduler import filters


class AvailabilityZoneFilter(abstract_filter.AbstractHostFilter):
class AvailabilityZoneFilter(filters.BaseHostFilter):
"""Filters Hosts by availabilty zone."""

def host_passes(self, host_state, filter_properties):
Expand Down
4 changes: 2 additions & 2 deletions nova/scheduler/filters/compute_filter.py
Expand Up @@ -14,14 +14,14 @@
# under the License.

from nova import log as logging
from nova.scheduler.filters import abstract_filter
from nova.scheduler import filters
from nova import utils


LOG = logging.getLogger(__name__)


class ComputeFilter(abstract_filter.AbstractHostFilter):
class ComputeFilter(filters.BaseHostFilter):
"""HostFilter hard-coded to work with InstanceType records."""

def _satisfies_extra_specs(self, capabilities, instance_type):
Expand Down
4 changes: 2 additions & 2 deletions nova/scheduler/filters/core_filter.py
Expand Up @@ -18,7 +18,7 @@
from nova import flags
from nova import log as logging
from nova.openstack.common import cfg
from nova.scheduler.filters import abstract_filter
from nova.scheduler import filters


LOG = logging.getLogger(__name__)
Expand All @@ -31,7 +31,7 @@
FLAGS.register_opt(cpu_allocation_ratio_opt)


class CoreFilter(abstract_filter.AbstractHostFilter):
class CoreFilter(filters.BaseHostFilter):
"""CoreFilter filters based on CPU core utilization."""

def host_passes(self, host_state, filter_properties):
Expand Down
4 changes: 2 additions & 2 deletions nova/scheduler/filters/isolated_hosts_filter.py
Expand Up @@ -14,14 +14,14 @@
# under the License.


import abstract_filter
from nova import flags
from nova.scheduler import filters


FLAGS = flags.FLAGS


class IsolatedHostsFilter(abstract_filter.AbstractHostFilter):
class IsolatedHostsFilter(filters.BaseHostFilter):
"""Returns host."""

def host_passes(self, host_state, filter_properties):
Expand Down
4 changes: 2 additions & 2 deletions nova/scheduler/filters/json_filter.py
Expand Up @@ -17,10 +17,10 @@
import json
import operator

from nova.scheduler.filters import abstract_filter
from nova.scheduler import filters


class JsonFilter(abstract_filter.AbstractHostFilter):
class JsonFilter(filters.BaseHostFilter):
"""Host Filter to allow simple JSON-based grammar for
selecting hosts.
"""
Expand Down
4 changes: 2 additions & 2 deletions nova/scheduler/filters/ram_filter.py
Expand Up @@ -17,7 +17,7 @@
from nova import flags
from nova import log as logging
from nova.openstack.common import cfg
from nova.scheduler.filters import abstract_filter
from nova.scheduler import filters

LOG = logging.getLogger(__name__)

Expand All @@ -29,7 +29,7 @@
FLAGS.register_opt(ram_allocation_ratio_opt)


class RamFilter(abstract_filter.AbstractHostFilter):
class RamFilter(filters.BaseHostFilter):
"""Ram Filter with over subscription flag"""

def host_passes(self, host_state, filter_properties):
Expand Down
32 changes: 13 additions & 19 deletions nova/scheduler/host_manager.py
Expand Up @@ -18,14 +18,14 @@
"""

import datetime
import types
import UserDict

from nova import db
from nova import exception
from nova import flags
from nova import log as logging
from nova.openstack.common import cfg
from nova.scheduler import filters
from nova import utils


Expand All @@ -36,14 +36,20 @@
cfg.IntOpt('reserved_host_memory_mb',
default=512,
help='Amount of memory in MB to reserve for host/dom0'),
cfg.ListOpt('default_host_filters',
cfg.MultiStrOpt('scheduler_available_filters',
default=['nova.scheduler.filters.standard_filters'],
help='Filter classes available to the scheduler which may '
'be specified more than once. An entry of '
'"nova.scheduler.filters.standard_filters" '
'maps to all filters included with nova.'),
cfg.ListOpt('scheduler_default_filters',
default=[
'AvailabilityZoneFilter',
'RamFilter',
'ComputeFilter'
],
help='Which filters to use for filtering hosts when not '
'specified in the request.'),
help='Which filter class names to use for filtering hosts '
'when not specified in the request.'),
]

FLAGS = flags.FLAGS
Expand Down Expand Up @@ -157,20 +163,8 @@ class HostManager(object):

def __init__(self):
self.service_states = {} # { <host> : { <service> : { cap k : v }}}
self.filter_classes = self._get_filter_classes()

def _get_filter_classes(self):
"""Get the list of possible filter classes"""
# Imported here to avoid circular imports
from nova.scheduler import filters

def get_itm(nm):
return getattr(filters, nm)

return [get_itm(itm) for itm in dir(filters)
if (type(get_itm(itm)) is types.TypeType)
and issubclass(get_itm(itm), filters.AbstractHostFilter)
and get_itm(itm) is not filters.AbstractHostFilter]
self.filter_classes = filters.get_filter_classes(
FLAGS.scheduler_available_filters)

def _choose_host_filters(self, filters):
"""Since the caller may specify which filters to use we need
Expand All @@ -179,7 +173,7 @@ def _choose_host_filters(self, filters):
of acceptable filters.
"""
if filters is None:
filters = FLAGS.default_host_filters
filters = FLAGS.scheduler_default_filters
if not isinstance(filters, (list, tuple)):
filters = [filters]
good_filters = []
Expand Down

0 comments on commit e5ad06a

Please sign in to comment.