From 18917d9e0d6fe250cc3b4f7301d37a6c5f5faffb Mon Sep 17 00:00:00 2001 From: Gary Kotton Date: Sun, 6 Apr 2014 05:54:23 -0700 Subject: [PATCH] Scheduler: throw exception if no configured affinity filter If the scheduler hint indicates that the scheduling should perform either anti-affinity or affinity scheduling and the relevant filter is not configured then a NoValidHost exception will be thrown. This is valuable if an existing OpenStack installation is running and these filters are not defined after an upgrade. Change-Id: I79bb44ad7481b3ff924687a8d6afdd6c715c0b59 Closes-bug: #1302238 --- nova/scheduler/filter_scheduler.py | 17 +++++- nova/scheduler/utils.py | 5 ++ nova/tests/scheduler/test_filter_scheduler.py | 59 ++++++++++++++++--- nova/tests/scheduler/test_scheduler_utils.py | 6 ++ 4 files changed, 76 insertions(+), 11 deletions(-) diff --git a/nova/scheduler/filter_scheduler.py b/nova/scheduler/filter_scheduler.py index 35220ea64c..cd0e5654f9 100644 --- a/nova/scheduler/filter_scheduler.py +++ b/nova/scheduler/filter_scheduler.py @@ -61,6 +61,10 @@ class FilterScheduler(driver.Scheduler): self.options = scheduler_options.SchedulerOptions() self.compute_rpcapi = compute_rpcapi.ComputeAPI() self.notifier = rpc.get_notifier('scheduler') + self._supports_affinity = scheduler_utils.validate_filter( + 'ServerGroupAffinityFilter') + self._supports_anti_affinity = scheduler_utils.validate_filter( + 'ServerGroupAntiAffinityFilter') # NOTE(alaski): Remove this method when the scheduler rpc interface is # bumped to 4.x as it is no longer used. @@ -202,8 +206,7 @@ class FilterScheduler(driver.Scheduler): if pci_requests: filter_properties['pci_requests'] = pci_requests - @staticmethod - def _setup_instance_group(context, filter_properties): + def _setup_instance_group(self, context, filter_properties): update_group_hosts = False scheduler_hints = filter_properties.get('scheduler_hints') or {} group_hint = scheduler_hints.get('group', None) @@ -211,6 +214,16 @@ class FilterScheduler(driver.Scheduler): group = objects.InstanceGroup.get_by_hint(context, group_hint) policies = set(('anti-affinity', 'affinity')) if any((policy in policies) for policy in group.policies): + if ('affinity' in group.policies and + not self._supports_affinity): + msg = _("ServerGroupAffinityFilter not configured") + LOG.error(msg) + raise exception.NoValidHost(reason=msg) + if ('anti-affinity' in group.policies and + not self._supports_anti_affinity): + msg = _("ServerGroupAntiAffinityFilter not configured") + LOG.error(msg) + raise exception.NoValidHost(reason=msg) update_group_hosts = True filter_properties.setdefault('group_hosts', set()) user_hosts = set(filter_properties['group_hosts']) diff --git a/nova/scheduler/utils.py b/nova/scheduler/utils.py index 2e7f4b67c5..77274fc0a0 100644 --- a/nova/scheduler/utils.py +++ b/nova/scheduler/utils.py @@ -235,3 +235,8 @@ def parse_options(opts, sep='=', converter=str, name=""): {'name': name, 'options': ", ".join(bad)}) return good + + +def validate_filter(filter): + """Validates that the filter is configured in the default filters.""" + return filter in CONF.scheduler_default_filters diff --git a/nova/tests/scheduler/test_filter_scheduler.py b/nova/tests/scheduler/test_filter_scheduler.py index 7551a7eec5..aff3354a6b 100644 --- a/nova/tests/scheduler/test_filter_scheduler.py +++ b/nova/tests/scheduler/test_filter_scheduler.py @@ -371,7 +371,7 @@ class FilterSchedulerTestCase(test_scheduler.SchedulerTestCase): self.assertEqual({'vcpus': 5}, host_state.limits) - def _create_server_group(self): + def _create_server_group(self, policy='anti-affinity'): instance = fake_instance.fake_instance_obj(self.context, params={'host': 'hostA'}) @@ -379,10 +379,11 @@ class FilterSchedulerTestCase(test_scheduler.SchedulerTestCase): group.name = 'pele' group.uuid = str(uuid.uuid4()) group.members = [instance.uuid] - group.policies = ['anti-affinity'] + group.policies = [policy] return group - def _test_group_details_in_filter_properties(self, group, func, hint): + def _group_details_in_filter_properties(self, group, func='get_by_uuid', + hint=None, policy=None): sched = fakes.FakeFilterScheduler() filter_properties = { @@ -397,23 +398,63 @@ class FilterSchedulerTestCase(test_scheduler.SchedulerTestCase): mock.patch.object(objects.InstanceGroup, 'get_hosts', return_value=['hostA']), ) as (get_group, get_hosts): + sched._supports_anti_affinity = True update_group_hosts = sched._setup_instance_group(self.context, filter_properties) self.assertTrue(update_group_hosts) self.assertEqual(set(['hostA', 'hostB']), filter_properties['group_hosts']) - self.assertEqual(['anti-affinity'], - filter_properties['group_policies']) + self.assertEqual([policy], filter_properties['group_policies']) + + def test_group_details_in_filter_properties(self): + for policy in ['affinity', 'anti-affinity']: + group = self._create_server_group(policy) + self._group_details_in_filter_properties(group, func='get_by_uuid', + hint=group.uuid, + policy=policy) + + def _group_filter_with_filter_not_configured(self, policy): + self.flags(scheduler_default_filters=['f1', 'f2']) + sched = fakes.FakeFilterScheduler() + + instance = fake_instance.fake_instance_obj(self.context, + params={'host': 'hostA'}) + + group = objects.InstanceGroup() + group.uuid = str(uuid.uuid4()) + group.members = [instance.uuid] + group.policies = [policy] + + filter_properties = { + 'scheduler_hints': { + 'group': group.uuid, + }, + } + + with contextlib.nested( + mock.patch.object(objects.InstanceGroup, 'get_by_uuid', + return_value=group), + mock.patch.object(objects.InstanceGroup, 'get_hosts', + return_value=['hostA']), + ) as (get_group, get_hosts): + self.assertRaises(exception.NoValidHost, + sched._setup_instance_group, self.context, + filter_properties) + + def test_group_filter_with_filter_not_configured(self): + policies = ['anti-affinity', 'affinity'] + for policy in policies: + self._group_filter_with_filter_not_configured(policy) def test_group_uuid_details_in_filter_properties(self): group = self._create_server_group() - self._test_group_details_in_filter_properties(group, 'get_by_uuid', - group.uuid) + self._group_details_in_filter_properties(group, 'get_by_uuid', + group.uuid, 'anti-affinity') def test_group_name_details_in_filter_properties(self): group = self._create_server_group() - self._test_group_details_in_filter_properties(group, 'get_by_name', - group.name) + self._group_details_in_filter_properties(group, 'get_by_name', + group.name, 'anti-affinity') def test_schedule_host_pool(self): """Make sure the scheduler_host_subset_size property works properly.""" diff --git a/nova/tests/scheduler/test_scheduler_utils.py b/nova/tests/scheduler/test_scheduler_utils.py index e7a391b033..4613a419f2 100644 --- a/nova/tests/scheduler/test_scheduler_utils.py +++ b/nova/tests/scheduler/test_scheduler_utils.py @@ -215,3 +215,9 @@ class SchedulerUtilsTestCase(test.NoDBTestCase): '=', float, [('bar', -2.1)]) + + def test_validate_filters_configured(self): + self.flags(scheduler_default_filters='FakeFilter1,FakeFilter2') + self.assertTrue(scheduler_utils.validate_filter('FakeFilter1')) + self.assertTrue(scheduler_utils.validate_filter('FakeFilter2')) + self.assertFalse(scheduler_utils.validate_filter('FakeFilter3'))