From 0d9ce8319d75bfbd69fe4e0759aeed38e6054e56 Mon Sep 17 00:00:00 2001 From: Chris Behrens Date: Fri, 9 Nov 2012 01:57:42 +0000 Subject: Refactor scheduling filters This changes the scheduling filters to use the code in nova/loadables.py to locate filtering classes. This also creates some base functionality in nova/filters.py to be used by both the host scheduler and the cells scheduler. The scheduler_available_filters default has changed to 'nova.scheduler.filters.all_filters' which is better named compared to the old setting of 'standard_filters'. The old method is still supported for those that have put it explicitly in their configs, but it's marked as deprecated. DocImpact Change-Id: I84fdeafdba0275ab4b25f8857563bd7b1494bb69 --- nova/filters.py | 53 ++++++ nova/scheduler/filter_scheduler.py | 2 +- nova/scheduler/filters/__init__.py | 76 +++----- nova/scheduler/host_manager.py | 86 ++++----- nova/tests/scheduler/test_filter_scheduler.py | 6 +- nova/tests/scheduler/test_host_filters.py | 45 ++--- nova/tests/scheduler/test_host_manager.py | 249 ++++++++++++++------------ nova/tests/test_filters.py | 125 +++++++++++++ 8 files changed, 405 insertions(+), 237 deletions(-) create mode 100644 nova/filters.py create mode 100644 nova/tests/test_filters.py diff --git a/nova/filters.py b/nova/filters.py new file mode 100644 index 000000000..a3339eff8 --- /dev/null +++ b/nova/filters.py @@ -0,0 +1,53 @@ +# Copyright (c) 2011-2012 OpenStack, LLC. +# 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. + +""" +Filter support +""" + +from nova import loadables + + +class BaseFilter(object): + """Base class for all filter classes.""" + def _filter_one(self, obj, filter_properties): + """Return True if it passes the filter, False otherwise. + Override this in a subclass. + """ + return True + + 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 + decisions on all objects. Otherwise, one can just override + _filter_one() to filter a single object. + """ + for obj in filter_obj_list: + if self._filter_one(obj, filter_properties): + yield obj + + +class BaseFilterHandler(loadables.BaseLoader): + """Base class to handle loading filter classes. + + This class should be subclassed where one needs to use filters. + """ + + def get_filtered_objects(self, filter_classes, objs, + filter_properties): + for filter_cls in filter_classes: + objs = filter_cls().filter_all(objs, filter_properties) + return list(objs) diff --git a/nova/scheduler/filter_scheduler.py b/nova/scheduler/filter_scheduler.py index 3fdea1d59..10dc7e37c 100644 --- a/nova/scheduler/filter_scheduler.py +++ b/nova/scheduler/filter_scheduler.py @@ -268,7 +268,7 @@ class FilterScheduler(driver.Scheduler): num_instances = request_spec.get('num_instances', 1) for num in xrange(num_instances): # Filter local hosts based on requirements ... - hosts = self.host_manager.filter_hosts(hosts, + hosts = self.host_manager.get_filtered_hosts(hosts, filter_properties) if not hosts: # Can't get any more locally. diff --git a/nova/scheduler/filters/__init__.py b/nova/scheduler/filters/__init__.py index 2056f968e..6e8e7ea7b 100644 --- a/nova/scheduler/filters/__init__.py +++ b/nova/scheduler/filters/__init__.py @@ -17,71 +17,41 @@ Scheduler host filters """ -import os -import types +from nova import filters +from nova.openstack.common import log as logging -from nova import exception -from nova.openstack.common import importutils +LOG = logging.getLogger(__name__) -class BaseHostFilter(object): +class BaseHostFilter(filters.BaseFilter): """Base class for host filters.""" + def _filter_one(self, obj, filter_properties): + """Return True if the object passes the filter, otherwise False.""" + return self.host_passes(obj, filter_properties) def host_passes(self, host_state, filter_properties): + """Return True if the HostState passes the filter, otherwise False. + Override this in a subclass. + """ raise NotImplementedError() - def _full_name(self): - """module.classname of the filter.""" - return "%s.%s" % (self.__module__, self.__class__.__name__) +class HostFilterHandler(filters.BaseFilterHandler): + def __init__(self): + super(HostFilterHandler, self).__init__(BaseHostFilter) -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 all_filters(): + """Return a list of filter classes found in this directory. -def _get_filter_classes_from_module(module_name): - """Get all filter classes from a module.""" - classes = [] - module = importutils.import_module(module_name) - for obj_name in dir(module): - itm = getattr(module, obj_name) - if _is_filter_class(itm): - classes.append(itm) - return classes + This method is used as the default for available scheduler filters + and should return a list of all filter classes available. + """ + return HostFilterHandler().get_all_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 = importutils.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 + """Deprecated. Configs should change to use all_filters().""" + LOG.deprecated(_("Use 'nova.scheduler.filters.all_filters' instead " + "of 'nova.scheduler.filters.standard_filters'")) + return all_filters() diff --git a/nova/scheduler/host_manager.py b/nova/scheduler/host_manager.py index 2d6c25e9b..e93b529f9 100644 --- a/nova/scheduler/host_manager.py +++ b/nova/scheduler/host_manager.py @@ -31,7 +31,7 @@ from nova.scheduler import filters host_manager_opts = [ cfg.MultiStrOpt('scheduler_available_filters', - default=['nova.scheduler.filters.standard_filters'], + default=['nova.scheduler.filters.all_filters'], help='Filter classes available to the scheduler which may ' 'be specified more than once. An entry of ' '"nova.scheduler.filters.standard_filters" ' @@ -239,32 +239,6 @@ class HostState(object): def _statmap(self, stats): return dict((st['key'], st['value']) for st in stats) - def passes_filters(self, filter_fns, filter_properties): - """Return whether or not this host passes filters.""" - - if self.host in filter_properties.get('ignore_hosts', []): - LOG.debug(_('Host filter fails for ignored host %(host)s'), - {'host': self.host}) - return False - - force_hosts = filter_properties.get('force_hosts', []) - if force_hosts: - if not self.host in force_hosts: - LOG.debug(_('Host filter fails for non-forced host %(host)s'), - {'host': self.host}) - return self.host in force_hosts - - for filter_fn in filter_fns: - if not filter_fn(self, filter_properties): - LOG.debug(_('Host filter function %(func)s failed for ' - '%(host)s'), - {'func': repr(filter_fn), - 'host': self.host}) - return False - - LOG.debug(_('Host filter passes for %(host)s'), {'host': self.host}) - return True - def __repr__(self): return ("(%s, %s) ram:%s disk:%s io_ops:%s instances:%s vm_type:%s" % (self.host, self.nodename, self.free_ram_mb, self.free_disk_mb, @@ -281,32 +255,28 @@ class HostManager(object): # { (host, hypervisor_hostname) : { : { cap k : v }}} self.service_states = {} self.host_state_map = {} - self.filter_classes = filters.get_filter_classes( + self.filter_handler = filters.HostFilterHandler() + self.filter_classes = self.filter_handler.get_matching_classes( CONF.scheduler_available_filters) - def _choose_host_filters(self, filters): + def _choose_host_filters(self, filter_cls_names): """Since the caller may specify which filters to use we need to have an authoritative list of what is permissible. This function checks the filter names against a predefined set of acceptable filters. """ - if filters is None: - filters = CONF.scheduler_default_filters - if not isinstance(filters, (list, tuple)): - filters = [filters] + if filter_cls_names is None: + filter_cls_names = CONF.scheduler_default_filters + if not isinstance(filter_cls_names, (list, tuple)): + filter_cls_names = [filter_cls_names] good_filters = [] bad_filters = [] - for filter_name in filters: + for filter_name in filter_cls_names: found_class = False for cls in self.filter_classes: if cls.__name__ == filter_name: + good_filters.append(cls) found_class = True - filter_instance = cls() - # Get the filter function - filter_func = getattr(filter_instance, - 'host_passes', None) - if filter_func: - good_filters.append(filter_func) break if not found_class: bad_filters.append(filter_name) @@ -315,14 +285,36 @@ class HostManager(object): raise exception.SchedulerHostFilterNotFound(filter_name=msg) return good_filters - def filter_hosts(self, hosts, filter_properties, filters=None): + def get_filtered_hosts(self, hosts, filter_properties, + filter_class_names=None): """Filter hosts and return only ones passing all filters""" - filtered_hosts = [] - filter_fns = self._choose_host_filters(filters) - for host in hosts: - if host.passes_filters(filter_fns, filter_properties): - filtered_hosts.append(host) - return filtered_hosts + filter_classes = self._choose_host_filters(filter_class_names) + + hosts = set(hosts) + ignore_hosts = set(filter_properties.get('ignore_hosts', [])) + ignore_hosts = hosts & ignore_hosts + if ignore_hosts: + ignored_hosts = ', '.join(ignore_hosts) + msg = _('Host filter ignoring hosts: %(ignored_hosts)s') + LOG.debug(msg, locals()) + hosts = hosts - ignore_hosts + + force_hosts = set(filter_properties.get('force_hosts', [])) + if force_hosts: + matching_force_hosts = hosts & force_hosts + if not matching_force_hosts: + forced_hosts = ', '.join(force_hosts) + msg = _("No hosts matched due to not matching 'force_hosts'" + "value of '%(forced_hosts)s'") + LOG.debug(msg, locals()) + return [] + forced_hosts = ', '.join(matching_force_hosts) + msg = _('Host filter forcing available hosts to %(forced_hosts)s') + LOG.debug(msg, locals()) + hosts = matching_force_hosts + + return self.filter_handler.get_filtered_objects(filter_classes, + hosts, filter_properties) def update_service_capabilities(self, service_name, host, capabilities): """Update the per-service capabilities based on this notification.""" diff --git a/nova/tests/scheduler/test_filter_scheduler.py b/nova/tests/scheduler/test_filter_scheduler.py index 6add77cbe..b8ab45bc2 100644 --- a/nova/tests/scheduler/test_filter_scheduler.py +++ b/nova/tests/scheduler/test_filter_scheduler.py @@ -32,7 +32,7 @@ from nova.tests.scheduler import fakes from nova.tests.scheduler import test_scheduler -def fake_filter_hosts(hosts, filter_properties): +def fake_get_filtered_hosts(hosts, filter_properties): return list(hosts) @@ -155,8 +155,8 @@ class FilterSchedulerTestCase(test_scheduler.SchedulerTestCase): fake_context = context.RequestContext('user', 'project', is_admin=True) - self.stubs.Set(sched.host_manager, 'filter_hosts', - fake_filter_hosts) + self.stubs.Set(sched.host_manager, 'get_filtered_hosts', + fake_get_filtered_hosts) self.stubs.Set(least_cost, 'weighted_sum', _fake_weighted_sum) fakes.mox_host_manager_db_calls(self.mox, fake_context) diff --git a/nova/tests/scheduler/test_host_filters.py b/nova/tests/scheduler/test_host_filters.py index f19755b82..1f35ede9b 100644 --- a/nova/tests/scheduler/test_host_filters.py +++ b/nova/tests/scheduler/test_host_filters.py @@ -263,32 +263,33 @@ class HostFiltersTestCase(test.TestCase): self.json_query = jsonutils.dumps( ['and', ['>=', '$free_ram_mb', 1024], ['>=', '$free_disk_mb', 200 * 1024]]) - # This has a side effect of testing 'get_filter_classes' - # when specifying a method (in this case, our standard filters) - classes = filters.get_filter_classes( - ['nova.scheduler.filters.standard_filters']) + filter_handler = filters.HostFilterHandler() + classes = filter_handler.get_matching_classes( + ['nova.scheduler.filters.all_filters']) self.class_map = {} for cls in classes: self.class_map[cls.__name__] = cls - def test_get_filter_classes(self): - classes = filters.get_filter_classes( - ['nova.tests.scheduler.test_host_filters.TestFilter']) - self.assertEqual(len(classes), 1) - self.assertEqual(classes[0].__name__, 'TestFilter') - # Test a specific class along with our standard filters - classes = filters.get_filter_classes( - ['nova.tests.scheduler.test_host_filters.TestFilter', - 'nova.scheduler.filters.standard_filters']) - self.assertEqual(len(classes), 1 + len(self.class_map)) - - def test_get_filter_classes_raises_on_invalid_classes(self): - self.assertRaises(ImportError, - filters.get_filter_classes, - ['nova.tests.scheduler.test_host_filters.NoExist']) - self.assertRaises(exception.ClassNotFound, - filters.get_filter_classes, - ['nova.tests.scheduler.test_host_filters.TestBogusFilter']) + def test_standard_filters_is_deprecated(self): + info = {'called': False} + + def _fake_deprecated(*args, **kwargs): + info['called'] = True + + self.stubs.Set(filters.LOG, 'deprecated', _fake_deprecated) + + filter_handler = filters.HostFilterHandler() + filter_handler.get_matching_classes( + ['nova.scheduler.filters.standard_filters']) + + self.assertTrue(info['called']) + self.assertIn('AllHostsFilter', self.class_map) + self.assertIn('ComputeFilter', self.class_map) + + def test_all_filters(self): + # Double check at least a couple of known filters exist + self.assertIn('AllHostsFilter', self.class_map) + self.assertIn('ComputeFilter', self.class_map) def test_all_host_filter(self): filt_cls = self.class_map['AllHostsFilter']() diff --git a/nova/tests/scheduler/test_host_manager.py b/nova/tests/scheduler/test_host_manager.py index 0984cbf80..d12f1dea5 100644 --- a/nova/tests/scheduler/test_host_manager.py +++ b/nova/tests/scheduler/test_host_manager.py @@ -62,34 +62,146 @@ class HostManagerTestCase(test.TestCase): ComputeFilterClass2] # Test we returns 1 correct function - filter_fns = self.host_manager._choose_host_filters(None) - self.assertEqual(len(filter_fns), 1) - self.assertEqual(filter_fns[0].__func__, - ComputeFilterClass2.host_passes.__func__) - - def test_filter_hosts(self): - filters = ['fake-filter1', 'fake-filter2'] - fake_host1 = host_manager.HostState('host1', 'node1') - fake_host2 = host_manager.HostState('host2', 'node2') - hosts = [fake_host1, fake_host2] - filter_properties = {'fake_prop': 'fake_val'} - - self.mox.StubOutWithMock(self.host_manager, - '_choose_host_filters') - self.mox.StubOutWithMock(fake_host1, 'passes_filters') - self.mox.StubOutWithMock(fake_host2, 'passes_filters') - - self.host_manager._choose_host_filters(None).AndReturn(filters) - fake_host1.passes_filters(filters, filter_properties).AndReturn( - False) - fake_host2.passes_filters(filters, filter_properties).AndReturn( - True) + filter_classes = self.host_manager._choose_host_filters(None) + self.assertEqual(len(filter_classes), 1) + self.assertEqual(filter_classes[0].__name__, 'ComputeFilterClass2') + + def test_get_filtered_hosts(self): + fake_hosts = ['fake_host1', 'fake_host2', 'fake_host1', + 'fake_host1'] + fake_classes = 'fake_classes' + fake_properties = {'moo': 1, 'cow': 2} + expected_hosts = set(fake_hosts) + fake_result = 'fake_result' + + self.mox.StubOutWithMock(self.host_manager, '_choose_host_filters') + self.mox.StubOutWithMock(self.host_manager.filter_handler, + 'get_filtered_objects') + + self.host_manager._choose_host_filters(None).AndReturn(fake_classes) + self.host_manager.filter_handler.get_filtered_objects(fake_classes, + expected_hosts, fake_properties).AndReturn(fake_result) self.mox.ReplayAll() - filtered_hosts = self.host_manager.filter_hosts(hosts, - filter_properties, filters=None) - self.assertEqual(len(filtered_hosts), 1) - self.assertEqual(filtered_hosts[0], fake_host2) + + result = self.host_manager. get_filtered_hosts(fake_hosts, + fake_properties) + self.assertEqual(result, fake_result) + + def test_get_filtered_hosts_with_specificed_filters(self): + fake_hosts = ['fake_host1', 'fake_host2', 'fake_host1', + 'fake_host1'] + fake_classes = 'fake_classes' + fake_properties = {'moo': 1, 'cow': 2} + fake_filters = 'fake_filters' + expected_hosts = set(fake_hosts) + fake_result = 'fake_result' + + self.mox.StubOutWithMock(self.host_manager, '_choose_host_filters') + self.mox.StubOutWithMock(self.host_manager.filter_handler, + 'get_filtered_objects') + + self.host_manager._choose_host_filters(fake_filters).AndReturn( + fake_classes) + self.host_manager.filter_handler.get_filtered_objects(fake_classes, + expected_hosts, fake_properties).AndReturn(fake_result) + + self.mox.ReplayAll() + + result = self.host_manager.get_filtered_hosts(fake_hosts, + fake_properties, filter_class_names=fake_filters) + self.assertEqual(result, fake_result) + + def test_get_filtered_hosts_with_ignore(self): + fake_hosts = ['fake_host1', 'fake_host2', 'fake_host1', + 'fake_host1', 'fake_host3', 'fake_host4'] + fake_classes = 'fake_classes' + fake_properties = {'ignore_hosts': ['fake_host1', 'fake_host3', + 'fake_host5']} + expected_hosts = set(['fake_host2', 'fake_host4']) + fake_result = 'fake_result' + + self.mox.StubOutWithMock(self.host_manager, '_choose_host_filters') + self.mox.StubOutWithMock(self.host_manager.filter_handler, + 'get_filtered_objects') + + self.host_manager._choose_host_filters(None).AndReturn(fake_classes) + self.host_manager.filter_handler.get_filtered_objects(fake_classes, + expected_hosts, fake_properties).AndReturn(fake_result) + + self.mox.ReplayAll() + + result = self.host_manager.get_filtered_hosts(fake_hosts, + fake_properties) + self.assertEqual(result, fake_result) + + def test_get_filtered_hosts_with_force_hosts(self): + fake_hosts = ['fake_host1', 'fake_host2', 'fake_host1', + 'fake_host1', 'fake_host3', 'fake_host4'] + fake_classes = 'fake_classes' + fake_properties = {'force_hosts': ['fake_host1', 'fake_host3', + 'fake_host5']} + expected_hosts = set(['fake_host1', 'fake_host3']) + fake_result = 'fake_result' + + self.mox.StubOutWithMock(self.host_manager, '_choose_host_filters') + self.mox.StubOutWithMock(self.host_manager.filter_handler, + 'get_filtered_objects') + + self.host_manager._choose_host_filters(None).AndReturn(fake_classes) + self.host_manager.filter_handler.get_filtered_objects(fake_classes, + expected_hosts, fake_properties).AndReturn(fake_result) + + self.mox.ReplayAll() + + result = self.host_manager.get_filtered_hosts(fake_hosts, + fake_properties) + self.assertEqual(result, fake_result) + + def test_get_filtered_hosts_with_no_matching_force_hosts(self): + fake_hosts = ['fake_host1', 'fake_host2', 'fake_host1', + 'fake_host1', 'fake_host3', 'fake_host4'] + fake_classes = 'fake_classes' + fake_properties = {'force_hosts': ['fake_host5', 'fake_host6']} + expected_result = [] + + self.mox.StubOutWithMock(self.host_manager, '_choose_host_filters') + # Make sure this is not called. + self.mox.StubOutWithMock(self.host_manager.filter_handler, + 'get_filtered_objects') + + self.host_manager._choose_host_filters(None).AndReturn(fake_classes) + + self.mox.ReplayAll() + + result = self.host_manager.get_filtered_hosts(fake_hosts, + fake_properties) + self.assertEqual(result, expected_result) + + def test_get_filtered_hosts_with_ignore_and_force(self): + """Ensure ignore_hosts processed before force_hosts in host filters""" + fake_hosts = ['fake_host1', 'fake_host2', 'fake_host1', + 'fake_host1', 'fake_host3', 'fake_host4'] + fake_classes = 'fake_classes' + fake_properties = {'force_hosts': ['fake_host3', 'fake_host1'], + 'ignore_hosts': ['fake_host1']} + expected_hosts = set(['fake_host3']) + fake_result = 'fake_result' + + self.mox.StubOutWithMock(self.host_manager, '_choose_host_filters') + # Make sure this is not called. + self.mox.StubOutWithMock(self.host_manager.filter_handler, + 'get_filtered_objects') + self.host_manager.filter_handler.get_filtered_objects(fake_classes, + expected_hosts, fake_properties).AndReturn(fake_result) + + self.host_manager._choose_host_filters(None).AndReturn(fake_classes) + + self.mox.ReplayAll() + + result = self.host_manager.get_filtered_hosts(fake_hosts, + fake_properties) + self.assertEqual(result, fake_result) def test_update_service_capabilities(self): service_states = self.host_manager.service_states @@ -190,91 +302,6 @@ class HostStateTestCase(test.TestCase): # update_from_compute_node() and consume_from_instance() are tested # in HostManagerTestCase.test_get_all_host_states() - def test_host_state_passes_filters_passes(self): - fake_host = host_manager.HostState('host1', 'node1') - filter_properties = {} - - cls1 = ComputeFilterClass1() - cls2 = ComputeFilterClass2() - self.mox.StubOutWithMock(cls1, 'host_passes') - self.mox.StubOutWithMock(cls2, 'host_passes') - filter_fns = [cls1.host_passes, cls2.host_passes] - - cls1.host_passes(fake_host, filter_properties).AndReturn(True) - cls2.host_passes(fake_host, filter_properties).AndReturn(True) - - self.mox.ReplayAll() - result = fake_host.passes_filters(filter_fns, filter_properties) - self.assertTrue(result) - - def test_host_state_passes_filters_passes_with_ignore(self): - fake_host = host_manager.HostState('host1', 'node1') - filter_properties = {'ignore_hosts': ['host2']} - - cls1 = ComputeFilterClass1() - cls2 = ComputeFilterClass2() - self.mox.StubOutWithMock(cls1, 'host_passes') - self.mox.StubOutWithMock(cls2, 'host_passes') - filter_fns = [cls1.host_passes, cls2.host_passes] - - cls1.host_passes(fake_host, filter_properties).AndReturn(True) - cls2.host_passes(fake_host, filter_properties).AndReturn(True) - - self.mox.ReplayAll() - result = fake_host.passes_filters(filter_fns, filter_properties) - self.assertTrue(result) - - def test_host_state_passes_filters_fails(self): - fake_host = host_manager.HostState('host1', 'node1') - filter_properties = {} - - cls1 = ComputeFilterClass1() - cls2 = ComputeFilterClass2() - self.mox.StubOutWithMock(cls1, 'host_passes') - self.mox.StubOutWithMock(cls2, 'host_passes') - filter_fns = [cls1.host_passes, cls2.host_passes] - - cls1.host_passes(fake_host, filter_properties).AndReturn(False) - # cls2.host_passes() not called because of short circuit - - self.mox.ReplayAll() - result = fake_host.passes_filters(filter_fns, filter_properties) - self.assertFalse(result) - - def test_host_state_passes_filters_fails_from_ignore(self): - fake_host = host_manager.HostState('host1', 'node1') - filter_properties = {'ignore_hosts': ['host1']} - - cls1 = ComputeFilterClass1() - cls2 = ComputeFilterClass2() - self.mox.StubOutWithMock(cls1, 'host_passes') - self.mox.StubOutWithMock(cls2, 'host_passes') - filter_fns = [cls1.host_passes, cls2.host_passes] - - # cls[12].host_passes() not called because of short circuit - # with matching host to ignore - - self.mox.ReplayAll() - result = fake_host.passes_filters(filter_fns, filter_properties) - self.assertFalse(result) - - def test_host_state_passes_filters_skipped_from_force(self): - fake_host = host_manager.HostState('host1', 'node1') - filter_properties = {'force_hosts': ['host1']} - - cls1 = ComputeFilterClass1() - cls2 = ComputeFilterClass2() - self.mox.StubOutWithMock(cls1, 'host_passes') - self.mox.StubOutWithMock(cls2, 'host_passes') - filter_fns = [cls1.host_passes, cls2.host_passes] - - # cls[12].host_passes() not called because of short circuit - # with matching host to force - - self.mox.ReplayAll() - result = fake_host.passes_filters(filter_fns, filter_properties) - self.assertTrue(result) - def test_stat_consumption_from_compute_node(self): stats = [ dict(key='num_instances', value='5'), diff --git a/nova/tests/test_filters.py b/nova/tests/test_filters.py new file mode 100644 index 000000000..546b13180 --- /dev/null +++ b/nova/tests/test_filters.py @@ -0,0 +1,125 @@ +# Copyright 2012 OpenStack LLC. # 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. +""" +Tests For Scheduler Host Filters. +""" + +import inspect +import sys + +from nova import filters +from nova import loadables +from nova import test + + +class Filter1(filters.BaseFilter): + """Test Filter class #1.""" + pass + + +class Filter2(filters.BaseFilter): + """Test Filter class #2.""" + pass + + +class FiltersTestCase(test.TestCase): + def test_filter_all(self): + filter_obj_list = ['obj1', 'obj2', 'obj3'] + filter_properties = 'fake_filter_properties' + base_filter = filters.BaseFilter() + + self.mox.StubOutWithMock(base_filter, '_filter_one') + + base_filter._filter_one('obj1', filter_properties).AndReturn(True) + base_filter._filter_one('obj2', filter_properties).AndReturn(False) + base_filter._filter_one('obj3', filter_properties).AndReturn(True) + + self.mox.ReplayAll() + + result = base_filter.filter_all(filter_obj_list, filter_properties) + self.assertTrue(inspect.isgenerator(result)) + self.assertEqual(list(result), ['obj1', 'obj3']) + + def test_filter_all_recursive_yields(self): + """Test filter_all() allows generators from previous filter_all()s.""" + # filter_all() yields results. We want to make sure that we can + # call filter_all() with generators returned from previous calls + # to filter_all(). + filter_obj_list = ['obj1', 'obj2', 'obj3'] + filter_properties = 'fake_filter_properties' + base_filter = filters.BaseFilter() + + self.mox.StubOutWithMock(base_filter, '_filter_one') + + total_iterations = 200 + + # The order that _filter_one is going to get called gets + # confusing because we will be recursively yielding things.. + # We are going to simulate the first call to filter_all() + # returning False for 'obj2'. So, 'obj1' will get yielded + # 'total_iterations' number of times before the first filter_all() + # call gets to processing 'obj2'. We then return 'False' for it. + # After that, 'obj3' gets yielded 'total_iterations' number of + # times. + for x in xrange(total_iterations): + base_filter._filter_one('obj1', filter_properties).AndReturn(True) + base_filter._filter_one('obj2', filter_properties).AndReturn(False) + for x in xrange(total_iterations): + base_filter._filter_one('obj3', filter_properties).AndReturn(True) + self.mox.ReplayAll() + + objs = iter(filter_obj_list) + for x in xrange(total_iterations): + # Pass in generators returned from previous calls. + objs = base_filter.filter_all(objs, filter_properties) + self.assertTrue(inspect.isgenerator(objs)) + self.assertEqual(list(objs), ['obj1', 'obj3']) + + def test_get_filtered_objects(self): + filter_objs_initial = ['initial', 'filter1', 'objects1'] + filter_objs_second = ['second', 'filter2', 'objects2'] + filter_objs_last = ['last', 'filter3', 'objects3'] + filter_properties = 'fake_filter_properties' + + def _fake_base_loader_init(*args, **kwargs): + pass + + self.stubs.Set(loadables.BaseLoader, '__init__', + _fake_base_loader_init) + + filt1_mock = self.mox.CreateMock(Filter1) + filt2_mock = self.mox.CreateMock(Filter2) + + self.mox.StubOutWithMock(sys.modules[__name__], 'Filter1', + use_mock_anything=True) + self.mox.StubOutWithMock(filt1_mock, 'filter_all') + self.mox.StubOutWithMock(sys.modules[__name__], 'Filter2', + use_mock_anything=True) + self.mox.StubOutWithMock(filt2_mock, 'filter_all') + + Filter1().AndReturn(filt1_mock) + filt1_mock.filter_all(filter_objs_initial, + filter_properties).AndReturn(filter_objs_second) + Filter2().AndReturn(filt2_mock) + filt2_mock.filter_all(filter_objs_second, + filter_properties).AndReturn(filter_objs_last) + + self.mox.ReplayAll() + + filter_handler = filters.BaseFilterHandler(filters.BaseFilter) + filter_classes = [Filter1, Filter2] + result = filter_handler.get_filtered_objects(filter_classes, + filter_objs_initial, + filter_properties) + self.assertEqual(result, filter_objs_last) -- cgit