scheduler: fill RequestSpec.instance_group.members

New key group_members added to the filter_properties which is used to
populate the members field of the InstanceGroup referred by the
RequestSpec object during scheduling. This is necessary as the
new soft-affinity and soft-anti-affinty weighers use this information
to calculate the weight of the host.

This patch also ensures that if soft-affinity or soft-anti-affinity
is requested and the proper Weigher is not configured then
an exception is raised with an understandable error message.

Change-Id: I65b7a568ee185069a7c785ee2633a0157690e3f6
Implements: blueprint soft-affinity-for-server-group
This commit is contained in:
Balazs Gibizer 2015-11-30 14:56:22 +01:00
parent 72ba18468e
commit e3396e1edc
4 changed files with 76 additions and 33 deletions

View File

@ -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:

View File

@ -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):

View File

@ -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 = {}

View File

@ -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')