Make scheduler filters/weighers only load once

Right now, filters/weighers are instantiated on every invocation of the
scheduler. This is both time consuming and unnecessary. In cases where
a filter/weigher tries to be smart and store/cache something in between
invocations this actually prohibits that.

This change make base filter/weigher functions take objects instead of
classes and then let schedulers create objects only once and then reuse
them.

This fixes a known bug in trusted_filter that tries to cache things.

Related to blueprint scheduler-optimization

Change-Id: I3174ab7968b51c43c0711033bac5d4bc30938b95
Closes-Bug: #1223450
This commit is contained in:
Hans Lindgren 2014-10-23 19:42:35 +02:00
parent 551cf196df
commit c126d36640
11 changed files with 77 additions and 77 deletions

View File

@ -73,11 +73,13 @@ class CellsScheduler(base.Base):
self.compute_api = compute.API()
self.compute_task_api = conductor.ComputeTaskAPI()
self.filter_handler = filters.CellFilterHandler()
self.filter_classes = self.filter_handler.get_matching_classes(
filter_classes = self.filter_handler.get_matching_classes(
CONF.cells.scheduler_filter_classes)
self.filters = [cls() for cls in filter_classes]
self.weight_handler = weights.CellWeightHandler()
self.weigher_classes = self.weight_handler.get_matching_classes(
weigher_classes = self.weight_handler.get_matching_classes(
CONF.cells.scheduler_weight_classes)
self.weighers = [cls() for cls in weigher_classes]
def _create_instances_here(self, ctxt, instance_uuids, instance_properties,
instance_type, image, security_groups, block_device_mapping):
@ -142,8 +144,7 @@ class CellsScheduler(base.Base):
def _grab_target_cells(self, filter_properties):
cells = self._get_possible_cells()
cells = self.filter_handler.get_filtered_objects(self.filter_classes,
cells,
cells = self.filter_handler.get_filtered_objects(self.filters, cells,
filter_properties)
# NOTE(comstud): I know this reads weird, but the 'if's are nested
# this way to optimize for the common case where 'cells' is a list
@ -156,7 +157,7 @@ class CellsScheduler(base.Base):
raise exception.NoCellsAvailable()
weighted_cells = self.weight_handler.get_weighed_objects(
self.weigher_classes, cells, filter_properties)
self.weighers, cells, filter_properties)
LOG.debug("Weighted cells: %(weighted_cells)s",
{'weighted_cells': weighted_cells})
target_cells = [cell.obj for cell in weighted_cells]

View File

@ -64,20 +64,15 @@ class BaseFilterHandler(loadables.BaseLoader):
This class should be subclassed where one needs to use filters.
"""
def get_filtered_objects(self, filter_classes, objs,
filter_properties, index=0):
def get_filtered_objects(self, filters, objs, filter_properties, index=0):
list_objs = list(objs)
LOG.debug("Starting with %d host(s)", len(list_objs))
for filter_cls in filter_classes:
cls_name = filter_cls.__name__
filter = filter_cls()
for filter in filters:
if filter.run_filter_for_index(index):
objs = filter.filter_all(list_objs,
filter_properties)
cls_name = filter.__class__.__name__
objs = filter.filter_all(list_objs, filter_properties)
if objs is None:
LOG.debug("Filter %(cls_name)s says to stop filtering",
{'cls_name': cls_name})
LOG.debug("Filter %s says to stop filtering", cls_name)
return
list_objs = list(objs)
if not list_objs:

View File

@ -281,11 +281,15 @@ class HostManager(object):
def __init__(self):
self.host_state_map = {}
self.filter_handler = filters.HostFilterHandler()
self.filter_classes = self.filter_handler.get_matching_classes(
filter_classes = self.filter_handler.get_matching_classes(
CONF.scheduler_available_filters)
self.filter_cls_map = dict(
(cls.__name__, cls) for cls in filter_classes)
self.filter_obj_map = {}
self.weight_handler = weights.HostWeightHandler()
self.weight_classes = self.weight_handler.get_matching_classes(
weigher_classes = self.weight_handler.get_matching_classes(
CONF.scheduler_weight_classes)
self.weighers = [cls() for cls in weigher_classes]
def _choose_host_filters(self, filter_cls_names):
"""Since the caller may specify which filters to use we need
@ -297,14 +301,17 @@ class HostManager(object):
filter_cls_names = CONF.scheduler_default_filters
if not isinstance(filter_cls_names, (list, tuple)):
filter_cls_names = [filter_cls_names]
cls_map = dict((cls.__name__, cls) for cls in self.filter_classes)
good_filters = []
bad_filters = []
for filter_name in filter_cls_names:
if filter_name not in cls_map:
bad_filters.append(filter_name)
continue
good_filters.append(cls_map[filter_name])
if filter_name not in self.filter_obj_map:
if filter_name not in self.filter_cls_map:
bad_filters.append(filter_name)
continue
filter_cls = self.filter_cls_map[filter_name]
self.filter_obj_map[filter_name] = filter_cls()
good_filters.append(self.filter_obj_map[filter_name])
if bad_filters:
msg = ", ".join(bad_filters)
raise exception.SchedulerHostFilterNotFound(filter_name=msg)
@ -357,7 +364,7 @@ class HostManager(object):
"'force_nodes' value of '%s'")
LOG.audit(msg % forced_nodes_str)
filter_classes = self._choose_host_filters(filter_class_names)
filters = self._choose_host_filters(filter_class_names)
ignore_hosts = filter_properties.get('ignore_hosts', [])
force_hosts = filter_properties.get('force_hosts', [])
force_nodes = filter_properties.get('force_nodes', [])
@ -381,12 +388,12 @@ class HostManager(object):
return name_to_cls_map.values()
hosts = name_to_cls_map.itervalues()
return self.filter_handler.get_filtered_objects(filter_classes,
return self.filter_handler.get_filtered_objects(filters,
hosts, filter_properties, index)
def get_weighed_hosts(self, hosts, weight_properties):
"""Weigh the hosts."""
return self.weight_handler.get_weighed_objects(self.weight_classes,
return self.weight_handler.get_weighed_objects(self.weighers,
hosts, weight_properties)
def get_all_host_states(self, context):

View File

@ -43,13 +43,14 @@ class _FilterTestClass(test.NoDBTestCase):
self.scheduler = self.msg_runner.scheduler
self.my_cell_state = self.msg_runner.state_manager.get_my_state()
self.filter_handler = filters.CellFilterHandler()
self.filter_classes = self.filter_handler.get_matching_classes(
filter_classes = self.filter_handler.get_matching_classes(
[self.filter_cls_name])
self.filters = [cls() for cls in filter_classes]
self.context = context.RequestContext('fake', 'fake',
is_admin=True)
def _filter_cells(self, cells, filter_properties):
return self.filter_handler.get_filtered_objects(self.filter_classes,
return self.filter_handler.get_filtered_objects(self.filters,
cells,
filter_properties)

View File

@ -51,11 +51,13 @@ class FakeFilterClass2(filters.BaseCellFilter):
class FakeWeightClass1(weights.BaseCellWeigher):
pass
def _weigh_object(self, obj, weight_properties):
pass
class FakeWeightClass2(weights.BaseCellWeigher):
pass
def _weigh_object(self, obj, weight_properties):
pass
class CellsSchedulerTestCase(test.TestCase):
@ -360,8 +362,8 @@ class CellsSchedulerTestCase(test.TestCase):
def fake_rpc_build_instances(ctxt, **host_sched_kwargs):
call_info['host_sched_kwargs'] = host_sched_kwargs
def fake_get_filtered_objs(filter_classes, cells, filt_properties):
call_info['filt_classes'] = filter_classes
def fake_get_filtered_objs(filters, cells, filt_properties):
call_info['filt_objects'] = filters
call_info['filt_cells'] = cells
call_info['filt_props'] = filt_properties
return cells
@ -411,7 +413,7 @@ class CellsSchedulerTestCase(test.TestCase):
'instance_type': 'fake_type'}
self.assertEqual(expected_filt_props, call_info['filt_props'])
self.assertEqual([FakeFilterClass1, FakeFilterClass2],
call_info['filt_classes'])
[obj.__class__ for obj in call_info['filt_objects']])
self.assertEqual([self.my_cell_state], call_info['filt_cells'])
def test_cells_filter_returning_none(self):
@ -475,8 +477,8 @@ class CellsSchedulerTestCase(test.TestCase):
def fake_rpc_build_instances(ctxt, **host_sched_kwargs):
call_info['host_sched_kwargs'] = host_sched_kwargs
def fake_get_weighed_objs(weight_classes, cells, filt_properties):
call_info['weight_classes'] = weight_classes
def fake_get_weighed_objs(weighers, cells, filt_properties):
call_info['weighers'] = weighers
call_info['weight_cells'] = cells
call_info['weight_props'] = filt_properties
return [weights.WeightedCell(cells[0], 0.0)]
@ -526,5 +528,5 @@ class CellsSchedulerTestCase(test.TestCase):
'instance_type': 'fake_type'}
self.assertEqual(expected_filt_props, call_info['weight_props'])
self.assertEqual([FakeWeightClass1, FakeWeightClass2],
call_info['weight_classes'])
[obj.__class__ for obj in call_info['weighers']])
self.assertEqual([self.my_cell_state], call_info['weight_cells'])

View File

@ -78,11 +78,12 @@ class _WeigherTestClass(test.NoDBTestCase):
def setUp(self):
super(_WeigherTestClass, self).setUp()
self.weight_handler = weights.CellWeightHandler()
self.weight_classes = self.weight_handler.get_matching_classes(
weigher_classes = self.weight_handler.get_matching_classes(
[self.weigher_cls_name])
self.weighers = [cls() for cls in weigher_classes]
def _get_weighed_cells(self, cells, weight_properties):
return self.weight_handler.get_weighed_objects(self.weight_classes,
return self.weight_handler.get_weighed_objects(self.weighers,
cells, weight_properties)

View File

@ -110,11 +110,9 @@ class FiltersTestCase(test.NoDBTestCase):
self.mox.StubOutWithMock(filt2_mock, 'run_filter_for_index')
self.mox.StubOutWithMock(filt2_mock, 'filter_all')
Filter1().AndReturn(filt1_mock)
filt1_mock.run_filter_for_index(0).AndReturn(True)
filt1_mock.filter_all(filter_objs_initial,
filter_properties).AndReturn(filter_objs_second)
Filter2().AndReturn(filt2_mock)
filt2_mock.run_filter_for_index(0).AndReturn(True)
filt2_mock.filter_all(filter_objs_second,
filter_properties).AndReturn(filter_objs_last)
@ -122,8 +120,8 @@ class FiltersTestCase(test.NoDBTestCase):
self.mox.ReplayAll()
filter_handler = filters.BaseFilterHandler(filters.BaseFilter)
filter_classes = [Filter1, Filter2]
result = filter_handler.get_filtered_objects(filter_classes,
filter_mocks = [filt1_mock, filt2_mock]
result = filter_handler.get_filtered_objects(filter_mocks,
filter_objs_initial,
filter_properties)
self.assertEqual(filter_objs_last, result)
@ -154,19 +152,17 @@ class FiltersTestCase(test.NoDBTestCase):
self.mox.StubOutWithMock(filt2_mock, 'run_filter_for_index')
self.mox.StubOutWithMock(filt2_mock, 'filter_all')
Filter1().AndReturn(filt1_mock)
filt1_mock.run_filter_for_index(0).AndReturn(True)
filt1_mock.filter_all(filter_objs_initial,
filter_properties).AndReturn(filter_objs_second)
Filter2().AndReturn(filt2_mock)
# return false so filter_all will not be called
filt2_mock.run_filter_for_index(0).AndReturn(False)
self.mox.ReplayAll()
filter_handler = filters.BaseFilterHandler(filters.BaseFilter)
filter_classes = [Filter1, Filter2]
filter_handler.get_filtered_objects(filter_classes,
filter_mocks = [filt1_mock, filt2_mock]
filter_handler.get_filtered_objects(filter_mocks,
filter_objs_initial,
filter_properties)
@ -192,15 +188,14 @@ class FiltersTestCase(test.NoDBTestCase):
use_mock_anything=True)
self.mox.StubOutWithMock(filt2_mock, 'filter_all')
Filter1().AndReturn(filt1_mock)
filt1_mock.run_filter_for_index(0).AndReturn(True)
filt1_mock.filter_all(filter_objs_initial,
filter_properties).AndReturn(None)
self.mox.ReplayAll()
filter_handler = filters.BaseFilterHandler(filters.BaseFilter)
filter_classes = [Filter1, Filter2]
result = filter_handler.get_filtered_objects(filter_classes,
filter_mocks = [filt1_mock, filt2_mock]
result = filter_handler.get_filtered_objects(filter_mocks,
filter_objs_initial,
filter_properties)
self.assertIsNone(result)

View File

@ -49,6 +49,9 @@ class HostManagerTestCase(test.NoDBTestCase):
def setUp(self):
super(HostManagerTestCase, self).setUp()
self.flags(scheduler_available_filters=['%s.%s' % (__name__, cls) for
cls in ['FakeFilterClass1',
'FakeFilterClass2']])
self.host_manager = host_manager.HostManager()
self.fake_hosts = [host_manager.HostState('fake_host%s' % x,
'fake-node') for x in xrange(1, 5)]
@ -57,20 +60,16 @@ class HostManagerTestCase(test.NoDBTestCase):
def test_choose_host_filters_not_found(self):
self.flags(scheduler_default_filters='FakeFilterClass3')
self.host_manager.filter_classes = [FakeFilterClass1,
FakeFilterClass2]
self.assertRaises(exception.SchedulerHostFilterNotFound,
self.host_manager._choose_host_filters, None)
def test_choose_host_filters(self):
self.flags(scheduler_default_filters=['FakeFilterClass2'])
self.host_manager.filter_classes = [FakeFilterClass1,
FakeFilterClass2]
# Test we returns 1 correct function
filter_classes = self.host_manager._choose_host_filters(None)
self.assertEqual(len(filter_classes), 1)
self.assertEqual(filter_classes[0].__name__, 'FakeFilterClass2')
host_filters = self.host_manager._choose_host_filters(None)
self.assertEqual(len(host_filters), 1)
self.assertEqual(host_filters[0].__class__.__name__,
'FakeFilterClass2')
def _mock_get_filtered_hosts(self, info, specified_filters=None):
self.mox.StubOutWithMock(self.host_manager, '_choose_host_filters')
@ -85,7 +84,7 @@ class HostManagerTestCase(test.NoDBTestCase):
self.stubs.Set(FakeFilterClass1, '_filter_one', fake_filter_one)
self.host_manager._choose_host_filters(specified_filters).AndReturn(
[FakeFilterClass1])
[FakeFilterClass1()])
def _verify_result(self, info, result, filters=True):
for x in info['got_fprops']:

View File

@ -207,6 +207,9 @@ class IronicHostManagerTestFilters(test.NoDBTestCase):
def setUp(self):
super(IronicHostManagerTestFilters, self).setUp()
self.flags(scheduler_available_filters=['%s.%s' % (__name__, cls) for
cls in ['FakeFilterClass1',
'FakeFilterClass2']])
self.host_manager = ironic_host_manager.IronicHostManager()
self.fake_hosts = [ironic_host_manager.IronicNodeState(
'fake_host%s' % x, 'fake-node') for x in range(1, 5)]
@ -215,20 +218,17 @@ class IronicHostManagerTestFilters(test.NoDBTestCase):
def test_choose_host_filters_not_found(self):
self.flags(scheduler_default_filters='FakeFilterClass3')
self.host_manager.filter_classes = [FakeFilterClass1,
FakeFilterClass2]
self.assertRaises(exception.SchedulerHostFilterNotFound,
self.host_manager._choose_host_filters, None)
def test_choose_host_filters(self):
self.flags(scheduler_default_filters=['FakeFilterClass2'])
self.host_manager.filter_classes = [FakeFilterClass1,
FakeFilterClass2]
# Test we returns 1 correct function
filter_classes = self.host_manager._choose_host_filters(None)
self.assertEqual(1, len(filter_classes))
self.assertEqual('FakeFilterClass2', filter_classes[0].__name__)
host_filters = self.host_manager._choose_host_filters(None)
self.assertEqual(1, len(host_filters))
self.assertEqual('FakeFilterClass2',
host_filters[0].__class__.__name__)
def _mock_get_filtered_hosts(self, info, specified_filters=None):
self.mox.StubOutWithMock(self.host_manager, '_choose_host_filters')
@ -243,7 +243,7 @@ class IronicHostManagerTestFilters(test.NoDBTestCase):
self.stubs.Set(FakeFilterClass1, '_filter_one', fake_filter_one)
self.host_manager._choose_host_filters(specified_filters).AndReturn(
[FakeFilterClass1])
[FakeFilterClass1()])
def _verify_result(self, info, result, filters=True):
for x in info['got_fprops']:

View File

@ -46,12 +46,12 @@ class RamWeigherTestCase(test.NoDBTestCase):
def setUp(self):
super(RamWeigherTestCase, self).setUp()
self.weight_handler = weights.HostWeightHandler()
self.weight_classes = [ram.RAMWeigher]
self.weighers = [ram.RAMWeigher()]
def _get_weighed_host(self, hosts, weight_properties=None):
if weight_properties is None:
weight_properties = {}
return self.weight_handler.get_weighed_objects(self.weight_classes,
return self.weight_handler.get_weighed_objects(self.weighers,
hosts, weight_properties)[0]
def _get_all_hosts(self):
@ -118,7 +118,7 @@ class RamWeigherTestCase(test.NoDBTestCase):
# negativehost: free_ram_mb=-512
# so, host4 should win
weights = self.weight_handler.get_weighed_objects(self.weight_classes,
weights = self.weight_handler.get_weighed_objects(self.weighers,
hostinfo_list, {})
weighed_host = weights[0]
@ -135,13 +135,14 @@ class MetricsWeigherTestCase(test.NoDBTestCase):
def setUp(self):
super(MetricsWeigherTestCase, self).setUp()
self.weight_handler = weights.HostWeightHandler()
self.weight_classes = [metrics.MetricsWeigher]
self.weighers = [metrics.MetricsWeigher()]
def _get_weighed_host(self, hosts, setting, weight_properties=None):
if not weight_properties:
weight_properties = {}
self.flags(weight_setting=setting, group='metrics')
return self.weight_handler.get_weighed_objects(self.weight_classes,
self.weighers[0]._parse_setting()
return self.weight_handler.get_weighed_objects(self.weighers,
hosts, weight_properties)[0]
def _get_all_hosts(self):
@ -227,7 +228,7 @@ class MetricsWeigherTestCase(test.NoDBTestCase):
self.assertIn(item, weigher.setting)
def test_parse_setting(self):
weigher = self.weight_classes[0]()
weigher = self.weighers[0]
self._check_parsing_result(weigher,
['foo=1'],
[('foo', 1.0)])
@ -270,12 +271,12 @@ class IoOpsWeigherTestCase(test.NoDBTestCase):
def setUp(self):
super(IoOpsWeigherTestCase, self).setUp()
self.weight_handler = weights.HostWeightHandler()
self.weight_classes = [io_ops.IoOpsWeigher]
self.weighers = [io_ops.IoOpsWeigher()]
def _get_weighed_host(self, hosts, io_ops_weight_multiplier):
if io_ops_weight_multiplier is not None:
self.flags(io_ops_weight_multiplier=io_ops_weight_multiplier)
return self.weight_handler.get_weighed_objects(self.weight_classes,
return self.weight_handler.get_weighed_objects(self.weighers,
hosts, {})[0]
def _get_all_hosts(self):

View File

@ -121,16 +121,14 @@ class BaseWeigher(object):
class BaseWeightHandler(loadables.BaseLoader):
object_class = WeighedObject
def get_weighed_objects(self, weigher_classes, obj_list,
weighing_properties):
def get_weighed_objects(self, weighers, obj_list, weighing_properties):
"""Return a sorted (descending), normalized list of WeighedObjects."""
if not obj_list:
return []
weighed_objs = [self.object_class(obj, 0.0) for obj in obj_list]
for weigher_cls in weigher_classes:
weigher = weigher_cls()
for weigher in weighers:
weights = weigher.weigh_objects(weighed_objs, weighing_properties)
# Normalize the weights