diff --git a/nova/objects/request_spec.py b/nova/objects/request_spec.py index 17e3f5f4390b..bfbc562f8cca 100644 --- a/nova/objects/request_spec.py +++ b/nova/objects/request_spec.py @@ -159,8 +159,10 @@ class RequestSpec(base.NovaObject): # NOTE(sbauza): Can be dropped once select_destinations is removed policies = list(filter_properties.get('group_policies')) hosts = list(filter_properties.get('group_hosts')) + members = list(filter_properties.get('group_members')) self.instance_group = objects.InstanceGroup(policies=policies, - hosts=hosts) + hosts=hosts, + members=members) # hosts has to be not part of the updates for saving the object self.instance_group.obj_reset_changes(['hosts']) else: diff --git a/nova/scheduler/utils.py b/nova/scheduler/utils.py index c91cdb8a3ad8..c5bd432ddd87 100644 --- a/nova/scheduler/utils.py +++ b/nova/scheduler/utils.py @@ -36,8 +36,11 @@ from nova import rpc LOG = logging.getLogger(__name__) CONF = nova.conf.CONF +CONF.import_opt('scheduler_default_filters', 'nova.scheduler.host_manager') +CONF.import_opt('scheduler_weight_classes', 'nova.scheduler.host_manager') -GroupDetails = collections.namedtuple('GroupDetails', ['hosts', 'policies']) +GroupDetails = collections.namedtuple('GroupDetails', ['hosts', 'policies', + 'members']) def build_request_spec(ctxt, image, instances, instance_type=None): @@ -248,8 +251,17 @@ def validate_filter(filter): return filter in CONF.scheduler_default_filters +def validate_weigher(weigher): + """Validates that the weigher is configured in the default weighers.""" + if 'nova.scheduler.weights.all_weighers' in CONF.scheduler_weight_classes: + return True + return weigher in CONF.scheduler_weight_classes + + _SUPPORTS_AFFINITY = None _SUPPORTS_ANTI_AFFINITY = None +_SUPPORTS_SOFT_AFFINITY = None +_SUPPORTS_SOFT_ANTI_AFFINITY = None def _get_group_details(context, instance_uuid, user_group_hosts=None): @@ -270,9 +282,17 @@ def _get_group_details(context, instance_uuid, user_group_hosts=None): if _SUPPORTS_ANTI_AFFINITY is None: _SUPPORTS_ANTI_AFFINITY = validate_filter( 'ServerGroupAntiAffinityFilter') - _supports_server_groups = any((_SUPPORTS_AFFINITY, - _SUPPORTS_ANTI_AFFINITY)) - if not _supports_server_groups or not instance_uuid: + global _SUPPORTS_SOFT_AFFINITY + if _SUPPORTS_SOFT_AFFINITY is None: + _SUPPORTS_SOFT_AFFINITY = validate_weigher( + 'nova.scheduler.weights.affinity.ServerGroupSoftAffinityWeigher') + global _SUPPORTS_SOFT_ANTI_AFFINITY + if _SUPPORTS_SOFT_ANTI_AFFINITY is None: + _SUPPORTS_SOFT_ANTI_AFFINITY = validate_weigher( + 'nova.scheduler.weights.affinity.' + 'ServerGroupSoftAntiAffinityWeigher') + + if not instance_uuid: return try: @@ -281,20 +301,31 @@ def _get_group_details(context, instance_uuid, user_group_hosts=None): except exception.InstanceGroupNotFound: return - policies = set(('anti-affinity', 'affinity')) + policies = set(('anti-affinity', 'affinity', 'soft-affinity', + 'soft-anti-affinity')) if any((policy in policies) for policy in group.policies): - if (not _SUPPORTS_AFFINITY and 'affinity' in group.policies): + if not _SUPPORTS_AFFINITY and 'affinity' in group.policies: msg = _("ServerGroupAffinityFilter not configured") LOG.error(msg) raise exception.UnsupportedPolicyException(reason=msg) - if (not _SUPPORTS_ANTI_AFFINITY and 'anti-affinity' in group.policies): + if not _SUPPORTS_ANTI_AFFINITY and 'anti-affinity' in group.policies: msg = _("ServerGroupAntiAffinityFilter not configured") LOG.error(msg) raise exception.UnsupportedPolicyException(reason=msg) + if (not _SUPPORTS_SOFT_AFFINITY + and 'soft-affinity' in group.policies): + msg = _("ServerGroupSoftAffinityWeigher not configured") + LOG.error(msg) + raise exception.UnsupportedPolicyException(reason=msg) + if (not _SUPPORTS_SOFT_ANTI_AFFINITY + and 'soft-anti-affinity' in group.policies): + msg = _("ServerGroupSoftAntiAffinityWeigher not configured") + LOG.error(msg) + raise exception.UnsupportedPolicyException(reason=msg) group_hosts = set(group.get_hosts()) user_hosts = set(user_group_hosts) if user_group_hosts else set() return GroupDetails(hosts=user_hosts | group_hosts, - policies=group.policies) + policies=group.policies, members=group.members) def setup_instance_group(context, request_spec, filter_properties): @@ -315,6 +346,7 @@ def setup_instance_group(context, request_spec, filter_properties): filter_properties['group_updated'] = True filter_properties['group_hosts'] = group_info.hosts filter_properties['group_policies'] = group_info.policies + filter_properties['group_members'] = group_info.members def retry_on_timeout(retries=1): diff --git a/nova/tests/unit/objects/test_request_spec.py b/nova/tests/unit/objects/test_request_spec.py index 55a945ec8f28..d06edf26c27e 100644 --- a/nova/tests/unit/objects/test_request_spec.py +++ b/nova/tests/unit/objects/test_request_spec.py @@ -216,12 +216,14 @@ class _TestRequestSpecObject(object): filt_props['group_updated'] = True filt_props['group_policies'] = set(['affinity']) filt_props['group_hosts'] = set(['fake1']) + filt_props['group_members'] = set(['fake-instance1']) spec = objects.RequestSpec() spec._populate_group_info(filt_props) self.assertIsInstance(spec.instance_group, objects.InstanceGroup) self.assertEqual(['affinity'], spec.instance_group.policies) self.assertEqual(['fake1'], spec.instance_group.hosts) + self.assertEqual(['fake-instance1'], spec.instance_group.members) def test_populate_group_info_missing_values(self): filt_props = {} diff --git a/nova/tests/unit/scheduler/test_scheduler_utils.py b/nova/tests/unit/scheduler/test_scheduler_utils.py index 4037d8c70248..a9437de445b7 100644 --- a/nova/tests/unit/scheduler/test_scheduler_utils.py +++ b/nova/tests/unit/scheduler/test_scheduler_utils.py @@ -234,6 +234,23 @@ class SchedulerUtilsTestCase(test.NoDBTestCase): self.assertTrue(scheduler_utils.validate_filter('FakeFilter2')) self.assertFalse(scheduler_utils.validate_filter('FakeFilter3')) + def test_validate_weighers_configured(self): + self.flags(scheduler_weight_classes= + ['ServerGroupSoftAntiAffinityWeigher', + 'FakeFilter1']) + + self.assertTrue(scheduler_utils.validate_weigher( + 'ServerGroupSoftAntiAffinityWeigher')) + self.assertTrue(scheduler_utils.validate_weigher('FakeFilter1')) + self.assertFalse(scheduler_utils.validate_weigher( + 'ServerGroupSoftAffinityWeigher')) + + def test_validate_weighers_configured_all_weighers(self): + self.assertTrue(scheduler_utils.validate_weigher( + 'ServerGroupSoftAffinityWeigher')) + self.assertTrue(scheduler_utils.validate_weigher( + 'ServerGroupSoftAntiAffinityWeigher')) + def _create_server_group(self, policy='anti-affinity'): instance = fake_instance.fake_instance_obj(self.context, params={'host': 'hostA'}) @@ -259,35 +276,22 @@ class SchedulerUtilsTestCase(test.NoDBTestCase): group_info = scheduler_utils._get_group_details( self.context, 'fake_uuid', group_hosts) self.assertEqual( - (set(['hostA', 'hostB']), [policy]), + (set(['hostA', 'hostB']), [policy], group.members), group_info) def test_get_group_details(self): - for policy in ['affinity', 'anti-affinity']: + for policy in ['affinity', 'anti-affinity', + 'soft-affinity', 'soft-anti-affinity']: group = self._create_server_group(policy) self._get_group_details(group, policy=policy) - def test_get_group_details_with_no_affinity_filters(self): - self.flags(scheduler_default_filters=['fake']) - scheduler_utils._SUPPORTS_ANTI_AFFINITY = None - scheduler_utils._SUPPORTS_AFFINITY = None - group_info = scheduler_utils._get_group_details(self.context, - 'fake-uuid') - self.assertIsNone(group_info) - def test_get_group_details_with_no_instance_uuid(self): - self.flags(scheduler_default_filters=['fake']) - scheduler_utils._SUPPORTS_ANTI_AFFINITY = None - scheduler_utils._SUPPORTS_AFFINITY = None group_info = scheduler_utils._get_group_details(self.context, None) self.assertIsNone(group_info) def _get_group_details_with_filter_not_configured(self, policy): - wrong_filter = { - 'affinity': 'ServerGroupAntiAffinityFilter', - 'anti-affinity': 'ServerGroupAffinityFilter', - } - self.flags(scheduler_default_filters=[wrong_filter[policy]]) + self.flags(scheduler_default_filters=['fake']) + self.flags(scheduler_weight_classes=['fake']) instance = fake_instance.fake_instance_obj(self.context, params={'host': 'hostA'}) @@ -300,24 +304,26 @@ class SchedulerUtilsTestCase(test.NoDBTestCase): with test.nested( mock.patch.object(objects.InstanceGroup, 'get_by_instance_uuid', return_value=group), - mock.patch.object(objects.InstanceGroup, 'get_hosts', - return_value=['hostA']), - ) as (get_group, get_hosts): + ) as (get_group,): scheduler_utils._SUPPORTS_ANTI_AFFINITY = None scheduler_utils._SUPPORTS_AFFINITY = None + scheduler_utils._SUPPORTS_SOFT_AFFINITY = None + scheduler_utils._SUPPORTS_SOFT_ANTI_AFFINITY = None self.assertRaises(exception.UnsupportedPolicyException, scheduler_utils._get_group_details, self.context, 'fake-uuid') def test_get_group_details_with_filter_not_configured(self): - policies = ['anti-affinity', 'affinity'] + policies = ['anti-affinity', 'affinity', + 'soft-affinity', 'soft-anti-affinity'] for policy in policies: self._get_group_details_with_filter_not_configured(policy) @mock.patch.object(scheduler_utils, '_get_group_details') def test_setup_instance_group_in_filter_properties(self, mock_ggd): mock_ggd.return_value = scheduler_utils.GroupDetails( - hosts=set(['hostA', 'hostB']), policies=['policy']) + hosts=set(['hostA', 'hostB']), policies=['policy'], + members=['instance1']) spec = {'instance_properties': {'uuid': 'fake-uuid'}} filter_props = {'group_hosts': ['hostC']} @@ -327,7 +333,8 @@ class SchedulerUtilsTestCase(test.NoDBTestCase): ['hostC']) expected_filter_props = {'group_updated': True, 'group_hosts': set(['hostA', 'hostB']), - 'group_policies': ['policy']} + 'group_policies': ['policy'], + 'group_members': ['instance1']} self.assertEqual(expected_filter_props, filter_props) @mock.patch.object(scheduler_utils, '_get_group_details')