Add AggregateTypeAffinityFilter multi values support

This change allows the AggregateTypeAffinityFilter to function when
multiple instance_type names are set in the Aggregate Metadata.
This change implements and documents a new comma separated syntax for
the aggregate instance_type metadata attribute. The legacy syntax is
still supported when a single instace_type is specified.
e.g. 'm1.nano' or "m1.nano,m1.small"

DocImpact
Change-Id: I0618a300754d012db62df52faa12cc3cedfe2b65
Closes-bug: #1399204
This commit is contained in:
Sean Mooney 2015-01-12 16:57:01 +00:00 committed by Joe Gordon
parent 2a44b67dbb
commit 66e1427f14
3 changed files with 78 additions and 8 deletions

View File

@ -142,6 +142,11 @@ There are some standard filter classes to use (:mod:`nova.scheduler.filters`):
* |TypeAffinityFilter| - Only passes hosts that are not already running an
instance of the requested type.
* |AggregateTypeAffinityFilter| - limits instance_type by aggregate.
This filter passes hosts if no instance_type key is set or
the instance_type aggregate metadata value contains the name of the
instance_type requested. The value of the instance_type metadata entry is
a string that may contain either a single instance_type name or a comma
separated list of instance_type names. e.g. 'm1.nano' or "m1.nano,m1.small"
* |ServerGroupAntiAffinityFilter| - This filter implements anti-affinity for a
server group. First you must create a server group with a policy of
'anti-affinity' via the server groups API. Then, when you boot a new server,

View File

@ -54,6 +54,8 @@ class AggregateTypeAffinityFilter(filters.BaseHostFilter):
aggregate_vals = utils.aggregate_values_from_key(
host_state, 'instance_type')
if not aggregate_vals:
return True
return instance_type['name'] in aggregate_vals
for val in aggregate_vals:
if (instance_type['name'] in
[x.strip() for x in val.split(',')]):
return True
return not aggregate_vals

View File

@ -42,17 +42,80 @@ class TestTypeFilter(test.NoDBTestCase):
self.assertFalse(self.filt_cls.host_passes(host, filter_properties))
@mock.patch('nova.scheduler.filters.utils.aggregate_values_from_key')
def test_aggregate_type_filter(self, agg_mock):
def test_aggregate_type_filter_no_metadata(self, agg_mock):
self.filt_cls = type_filter.AggregateTypeAffinityFilter()
filter_properties = {'context': mock.sentinel.ctx,
'instance_type': {'name': 'fake1'}}
host = fakes.FakeHostState('fake_host', 'fake_node', {})
# tests when no instance_type is defined for aggregate
agg_mock.return_value = set([])
# True as no instance_type set for aggregate
self.assertTrue(self.filt_cls.host_passes(host, filter_properties))
agg_mock.assert_called_once_with(host, 'instance_type')
@mock.patch('nova.scheduler.filters.utils.aggregate_values_from_key')
def test_aggregate_type_filter_single_instance_type(self, agg_mock):
self.filt_cls = type_filter.AggregateTypeAffinityFilter()
filter_properties = {'context': mock.sentinel.ctx,
'instance_type': {'name': 'fake1'}}
filter2_properties = {'context': mock.sentinel.ctx,
'instance_type': {'name': 'fake2'}}
'instance_type': {'name': 'fake2'}}
host = fakes.FakeHostState('fake_host', 'fake_node', {})
# tests when a single instance_type is defined for an aggregate
# using legacy single value syntax
agg_mock.return_value = set(['fake1'])
# True since no aggregates
# True as instance_type is allowed for aggregate
self.assertTrue(self.filt_cls.host_passes(host, filter_properties))
agg_mock.assert_called_once_with(host, 'instance_type')
# False since type matches aggregate, metadata
# False as instance_type is not allowed for aggregate
self.assertFalse(self.filt_cls.host_passes(host, filter2_properties))
@mock.patch('nova.scheduler.filters.utils.aggregate_values_from_key')
def test_aggregate_type_filter_multi_aggregate(self, agg_mock):
self.filt_cls = type_filter.AggregateTypeAffinityFilter()
filter_properties = {'context': mock.sentinel.ctx,
'instance_type': {'name': 'fake1'}}
filter2_properties = {'context': mock.sentinel.ctx,
'instance_type': {'name': 'fake2'}}
filter3_properties = {'context': mock.sentinel.ctx,
'instance_type': {'name': 'fake3'}}
host = fakes.FakeHostState('fake_host', 'fake_node', {})
# tests when a single instance_type is defined for multiple aggregates
# using legacy single value syntax
agg_mock.return_value = set(['fake1', 'fake2'])
# True as instance_type is allowed for first aggregate
self.assertTrue(self.filt_cls.host_passes(host, filter_properties))
# True as instance_type is allowed for second aggregate
self.assertTrue(self.filt_cls.host_passes(host, filter2_properties))
# False as instance_type is not allowed for aggregates
self.assertFalse(self.filt_cls.host_passes(host, filter3_properties))
@mock.patch('nova.scheduler.filters.utils.aggregate_values_from_key')
def test_aggregate_type_filter_multi_instance_type(self, agg_mock):
self.filt_cls = type_filter.AggregateTypeAffinityFilter()
filter_properties = {'context': mock.sentinel.ctx,
'instance_type': {'name': 'fake1'}}
filter2_properties = {'context': mock.sentinel.ctx,
'instance_type': {'name': 'fake2'}}
filter3_properties = {'context': mock.sentinel.ctx,
'instance_type': {'name': 'fake3'}}
host = fakes.FakeHostState('fake_host', 'fake_node', {})
# tests when multiple instance_types are defined for aggregate
agg_mock.return_value = set(['fake1,fake2'])
# True as instance_type is allowed for aggregate
self.assertTrue(self.filt_cls.host_passes(host, filter_properties))
# True as instance_type is allowed for aggregate
self.assertTrue(self.filt_cls.host_passes(host, filter2_properties))
# False as instance_type is not allowed for aggregate
self.assertFalse(self.filt_cls.host_passes(host, filter3_properties))