Pull latest scheduler change from Oslo

The RetryFilter is now part of Oslo, although it's been renamed to
IgnoreAttemptedFilter.  Thanks to entry_point, we are able to maintain
backwards compatibility after pulling that commit from Oslo.

Commits in this change:
* 66fe978 2013-12-10 | Change IgnoreAttemptedHostFilter to expect 'retry' key (attempt-retry)
* 135dd00 2013-12-10 | Remove start index 0 in range()
* 45658e2 2013-12-09 | Fix violations of H302:import only modules
* 70004c6 2013-12-04 | Add IgnoreAttemptedHostsFilter to oslo
* 880acf7 2013-11-14 | Change capabilities filters to use resource type (capfilter_message)
* 06e9d98 2013-11-10 | Add some log messages to capabilities_filter.py
* 3970d46 2013-11-02 | Fix typos in oslo
* 8718763 2013-08-19 | Replace list with dict in AvailabilityZoneFilter.host_passes
* c0d052a 2013-07-12 | python3: Add basic compatibility support.
* e3545f8 2013-06-02 | Enable hacking H402 test
* 484a1df 2013-05-30 | Enable hacking H403 test
* 35660da 2013-05-30 | Enable hacking H401 test
* 5dcc43b 2013-05-05 | Break out common functionality for filters and weights
* 1f2aba5 2013-05-03 | Renames filter to base_filter and weight to base_weight

Change-Id: Ibeb685ef60e44cb6388fc460ee6a78255ed3dbae
This commit is contained in:
Zhiteng Huang 2014-01-02 09:31:24 +08:00
parent 5be4620ae5
commit 8ba08f0aa6
11 changed files with 100 additions and 96 deletions

View File

@ -17,9 +17,7 @@
Filter support
"""
import inspect
from stevedore import extension
from cinder.openstack.common.scheduler import base_handler
class BaseFilter(object):
@ -33,7 +31,7 @@ class BaseFilter(object):
def filter_all(self, filter_obj_list, filter_properties):
"""Yield objects that pass the filter.
Can be overriden in a subclass, if you need to base filtering
Can be overridden in a subclass, if you need to base filtering
decisions on all objects. Otherwise, one can just override
_filter_one() to filter a single object.
"""
@ -42,27 +40,11 @@ class BaseFilter(object):
yield obj
class BaseFilterHandler(object):
""" Base class to handle loading filter classes.
class BaseFilterHandler(base_handler.BaseHandler):
"""Base class to handle loading filter classes.
This class should be subclassed where one needs to use filters.
"""
def __init__(self, filter_class_type, filter_namespace):
self.namespace = filter_namespace
self.filter_class_type = filter_class_type
self.filter_manager = extension.ExtensionManager(filter_namespace)
def _is_correct_class(self, obj):
"""Return whether an object is a class of the correct type and
is not prefixed with an underscore.
"""
return (inspect.isclass(obj) and
not obj.__name__.startswith('_') and
issubclass(obj, self.filter_class_type))
def get_all_classes(self):
return [x.plugin for x in self.filter_manager
if self._is_correct_class(x.plugin)]
def get_filtered_objects(self, filter_classes, objs,
filter_properties):

View File

@ -0,0 +1,45 @@
# Copyright (c) 2011-2013 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.
"""
A common base for handling extension classes.
Used by BaseFilterHandler and BaseWeightHandler
"""
import inspect
from stevedore import extension
class BaseHandler(object):
"""Base class to handle loading filter and weight classes."""
def __init__(self, modifier_class_type, modifier_namespace):
self.namespace = modifier_namespace
self.modifier_class_type = modifier_class_type
self.extension_manager = extension.ExtensionManager(modifier_namespace)
def _is_correct_class(self, cls):
"""Return whether an object is a class of the correct type and
is not prefixed with an underscore.
"""
return (inspect.isclass(cls) and
not cls.__name__.startswith('_') and
issubclass(cls, self.modifier_class_type))
def get_all_classes(self):
# We use a set, as some classes may have an entrypoint of their own,
# and also be returned by a function such as 'all_filters' for example
return [ext.plugin for ext in self.extension_manager if
self._is_correct_class(ext.plugin)]

View File

@ -17,9 +17,7 @@
Pluggable Weighing support
"""
import inspect
from stevedore import extension
from cinder.openstack.common.scheduler import base_handler
class WeighedObject(object):
@ -36,7 +34,7 @@ class BaseWeigher(object):
"""Base class for pluggable weighers."""
def _weight_multiplier(self):
"""How weighted this weigher should be. Normally this would
be overriden in a subclass based on a config value.
be overridden in a subclass based on a config value.
"""
return 1.0
@ -56,26 +54,9 @@ class BaseWeigher(object):
self._weigh_object(obj.obj, weight_properties))
class BaseWeightHandler(object):
class BaseWeightHandler(base_handler.BaseHandler):
object_class = WeighedObject
def __init__(self, weighed_object_type, weight_namespace):
self.namespace = weight_namespace
self.weighed_object_type = weighed_object_type
self.weight_manager = extension.ExtensionManager(weight_namespace)
def _is_correct_class(self, obj):
"""Return whether an object is a class of the correct type and
is not prefixed with an underscore.
"""
return (inspect.isclass(obj) and
not obj.__name__.startswith('_') and
issubclass(obj, self.weighed_object_type))
def get_all_classes(self):
return [x.plugin for x in self.weight_manager
if self._is_correct_class(x.plugin)]
def get_weighed_objects(self, weigher_classes, obj_list,
weighing_properties):
"""Return a sorted (highest score first) list of WeighedObjects."""

View File

@ -18,12 +18,12 @@ Scheduler host filters
"""
from cinder.openstack.common import log as logging
from cinder.openstack.common.scheduler import filter
from cinder.openstack.common.scheduler import base_filter
LOG = logging.getLogger(__name__)
class BaseHostFilter(filter.BaseFilter):
class BaseHostFilter(base_filter.BaseFilter):
"""Base class for host filters."""
def _filter_one(self, obj, filter_properties):
"""Return True if the object passes the filter, otherwise False."""
@ -36,6 +36,6 @@ class BaseHostFilter(filter.BaseFilter):
raise NotImplementedError()
class HostFilterHandler(filter.BaseFilterHandler):
class HostFilterHandler(base_filter.BaseFilterHandler):
def __init__(self, namespace):
super(HostFilterHandler, self).__init__(BaseHostFilter, namespace)

View File

@ -22,7 +22,7 @@ class AvailabilityZoneFilter(filters.BaseHostFilter):
def host_passes(self, host_state, filter_properties):
spec = filter_properties.get('request_spec', {})
props = spec.get('resource_properties', [])
props = spec.get('resource_properties', {})
availability_zone = props.get('availability_zone')
if availability_zone:

View File

@ -13,11 +13,13 @@
# License for the specific language governing permissions and limitations
# under the License.
import six
from cinder.openstack.common.gettextutils import _ # noqa
from cinder.openstack.common import log as logging
from cinder.openstack.common.scheduler import filters
from cinder.openstack.common.scheduler.filters import extra_specs_ops
LOG = logging.getLogger(__name__)
@ -25,13 +27,14 @@ class CapabilitiesFilter(filters.BaseHostFilter):
"""HostFilter to work with resource (instance & volume) type records."""
def _satisfies_extra_specs(self, capabilities, resource_type):
"""Check that the capabilities provided by the services
satisfy the extra specs associated with the instance type"""
"""Check that the capabilities provided by the services satisfy
the extra specs associated with the resource type.
"""
extra_specs = resource_type.get('extra_specs', [])
if not extra_specs:
return True
for key, req in extra_specs.iteritems():
for key, req in six.iteritems(extra_specs):
# Either not scope format, or in capabilities scope
scope = key.split(':')
if len(scope) > 1 and scope[0] != "capabilities":
@ -40,7 +43,7 @@ class CapabilitiesFilter(filters.BaseHostFilter):
del scope[0]
cap = capabilities
for index in range(0, len(scope)):
for index in range(len(scope)):
try:
cap = cap.get(scope[index], None)
except AttributeError:
@ -48,16 +51,20 @@ class CapabilitiesFilter(filters.BaseHostFilter):
if cap is None:
return False
if not extra_specs_ops.match(cap, req):
LOG.debug(_("extra_spec requirement '%(req)s' does not match "
"'%(cap)s'"), {'req': req, 'cap': cap})
return False
return True
def host_passes(self, host_state, filter_properties):
"""Return a list of hosts that can create instance_type."""
"""Return a list of hosts that can create resource_type."""
# Note(zhiteng) Currently only Cinder and Nova are using
# this filter, so the resource type is either instance or
# volume.
resource_type = filter_properties.get('resource_type')
if not self._satisfies_extra_specs(host_state.capabilities,
resource_type):
LOG.debug(_("%(host_state)s fails resource_type extra_specs "
"requirements"), {'host_state': host_state})
return False
return True

View File

@ -1,4 +1,4 @@
# Copyright (c) 2012 OpenStack Foundation
# Copyright (c) 2011 OpenStack Foundation.
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
@ -13,34 +13,44 @@
# License for the specific language governing permissions and limitations
# under the License.
from cinder.openstack.common.gettextutils import _ # noqa
from cinder.openstack.common import log as logging
from cinder.openstack.common.scheduler import filters
LOG = logging.getLogger(__name__)
class RetryFilter(filters.BaseHostFilter):
"""Filter out nodes that have already been attempted for scheduling
purposes
class IgnoreAttemptedHostsFilter(filters.BaseHostFilter):
"""Filter out previously attempted hosts
A host passes this filter if it has not already been attempted for
scheduling. The scheduler needs to add previously attempted hosts
to the 'retry' key of filter_properties in order for this to work
correctly. For example:
{
'retry': {
'hosts': ['host1', 'host2'],
'num_attempts': 3,
}
}
"""
def host_passes(self, host_state, filter_properties):
"""Skip nodes that have already been attempted."""
retry = filter_properties.get('retry', None)
if not retry:
attempted = filter_properties.get('retry', None)
if not attempted:
# Re-scheduling is disabled
LOG.debug("Re-scheduling is disabled")
LOG.debug(_("Re-scheduling is disabled."))
return True
hosts = retry.get('hosts', [])
hosts = attempted.get('hosts', [])
host = host_state.host
passes = host not in hosts
pass_msg = "passes" if passes else "fails"
LOG.debug(_("Host %(host)s %(pass_msg)s. Previously tried hosts: "
"%(hosts)s") %
{'host': host, 'pass_msg': pass_msg, 'hosts': hosts})
# Host passes if it's not in the list of previously attempted hosts:
"%(hosts)s") % {'host': host,
'pass_msg': pass_msg,
'hosts': hosts})
return passes

View File

@ -16,6 +16,8 @@
import operator
import six
from cinder.openstack.common import jsonutils
from cinder.openstack.common.scheduler import filters
@ -51,7 +53,7 @@ class JsonFilter(filters.BaseHostFilter):
return self._op_compare(args, operator.gt)
def _in(self, args):
"""First term is in set of remaining terms"""
"""First term is in set of remaining terms."""
return self._op_compare(args, operator.contains)
def _less_than_equal(self, args):
@ -117,7 +119,7 @@ class JsonFilter(filters.BaseHostFilter):
for arg in query[1:]:
if isinstance(arg, list):
arg = self._process_filter(arg, host_state)
elif isinstance(arg, basestring):
elif isinstance(arg, six.string_types):
arg = self._parse_string(arg, host_state)
if arg is not None:
cooked_args.append(arg)

View File

@ -18,10 +18,10 @@ Scheduler host weights
"""
from cinder.openstack.common.scheduler import weight
from cinder.openstack.common.scheduler import base_weight
class WeighedHost(weight.WeighedObject):
class WeighedHost(base_weight.WeighedObject):
def to_dict(self):
return {
'weight': self.weight,
@ -33,12 +33,12 @@ class WeighedHost(weight.WeighedObject):
(self.obj.host, self.weight))
class BaseHostWeigher(weight.BaseWeigher):
class BaseHostWeigher(base_weight.BaseWeigher):
"""Base class for host weights."""
pass
class HostWeightHandler(weight.BaseWeightHandler):
class HostWeightHandler(base_weight.BaseWeightHandler):
object_class = WeighedHost
def __init__(self, namespace):

View File

@ -90,26 +90,3 @@ class HostFiltersTestCase(test.TestCase):
'updated_at': None,
'service': service})
self.assertTrue(filt_cls.host_passes(host, filter_properties))
def test_retry_filter_disabled(self):
# Test case where retry/re-scheduling is disabled.
filt_cls = self.class_map['RetryFilter']()
host = fakes.FakeHostState('host1', {})
filter_properties = {}
self.assertTrue(filt_cls.host_passes(host, filter_properties))
def test_retry_filter_pass(self):
# Node not previously tried.
filt_cls = self.class_map['RetryFilter']()
host = fakes.FakeHostState('host1', {})
retry = dict(num_attempts=2, hosts=['host2'])
filter_properties = dict(retry=retry)
self.assertTrue(filt_cls.host_passes(host, filter_properties))
def test_retry_filter_fail(self):
# Node was already tried.
filt_cls = self.class_map['RetryFilter']()
host = fakes.FakeHostState('host1', {})
retry = dict(num_attempts=1, hosts=['host1'])
filter_properties = dict(retry=retry)
self.assertFalse(filt_cls.host_passes(host, filter_properties))

View File

@ -44,7 +44,7 @@ cinder.scheduler.filters =
CapabilitiesFilter = cinder.openstack.common.scheduler.filters.capabilities_filter:CapabilitiesFilter
CapacityFilter = cinder.scheduler.filters.capacity_filter:CapacityFilter
JsonFilter = cinder.openstack.common.scheduler.filters.json_filter:JsonFilter
RetryFilter = cinder.scheduler.filters.retry_filter:RetryFilter
RetryFilter = cinder.openstack.common.scheduler.filters.ignore_attempted_hosts_filter:IgnoreAttemptedHostsFilter
cinder.scheduler.weights =
AllocatedCapacityWeigher = cinder.scheduler.weights.capacity:AllocatedCapacityWeigher
CapacityWeigher = cinder.scheduler.weights.capacity:CapacityWeigher