diff --git a/doc/source/filter_scheduler.rst b/doc/source/filter_scheduler.rst index a7ea5e2b71f2..bbd94019f2c5 100644 --- a/doc/source/filter_scheduler.rst +++ b/doc/source/filter_scheduler.rst @@ -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, diff --git a/nova/scheduler/filters/type_filter.py b/nova/scheduler/filters/type_filter.py index 7e6d90d63075..e593cf071a59 100644 --- a/nova/scheduler/filters/type_filter.py +++ b/nova/scheduler/filters/type_filter.py @@ -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 diff --git a/nova/tests/unit/scheduler/filters/test_type_filters.py b/nova/tests/unit/scheduler/filters/test_type_filters.py index fc4fc88c229d..8e05005b0f47 100644 --- a/nova/tests/unit/scheduler/filters/test_type_filters.py +++ b/nova/tests/unit/scheduler/filters/test_type_filters.py @@ -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))