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:
parent
72ba18468e
commit
e3396e1edc
|
@ -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:
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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 = {}
|
||||
|
|
|
@ -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')
|
||||
|
|
Loading…
Reference in New Issue