From defc4324b1fb2de1a30c07646536c3c5f89c98f4 Mon Sep 17 00:00:00 2001 From: Kiran Pawar Date: Fri, 11 Feb 2022 04:13:13 +0000 Subject: [PATCH] Extend AffinityWeigher to support shards. ServerGroupAffinityWeigher gives back a high weight if the compute node already contains servers of the same server-group. However, we want to add a semi-high weight when other compute nodes in the same shard contain servers of this server-group. Usecase is to give the user a way to schedule servers to the same shard based on previously spawned servers e.g. to replace them with the new ones. If the servers are in the same shard, there's no automatic volume-migration happening. --- nova/scheduler/weights/affinity.py | 34 ++++++++++++++ .../weights/test_weights_affinity.py | 44 +++++++++++++++---- 2 files changed, 69 insertions(+), 9 deletions(-) diff --git a/nova/scheduler/weights/affinity.py b/nova/scheduler/weights/affinity.py index 90297f51a4d..8e47cabdebc 100644 --- a/nova/scheduler/weights/affinity.py +++ b/nova/scheduler/weights/affinity.py @@ -55,6 +55,7 @@ def _weigh_object(self, host_state, request_spec): class ServerGroupSoftAffinityWeigher(_SoftAffinityWeigherBase): policy_name = 'soft-affinity' warning_sent = False + _SHARD_PREFIX = 'vc-' def weight_multiplier(self): if (CONF.filter_scheduler.soft_affinity_weight_multiplier < 0 and @@ -70,6 +71,39 @@ def weight_multiplier(self): return CONF.filter_scheduler.soft_affinity_weight_multiplier + def _weigh_object(self, host_state, request_spec): + weight = super(ServerGroupSoftAffinityWeigher, self)._weigh_object( + host_state, request_spec) + + # if the host contained servers from the same group, return that weight + if weight: + return weight + + # get shards of the host + host_shard_aggrs = [aggr for aggr in host_state.aggregates + if aggr.name.startswith(self._SHARD_PREFIX)] + if not host_shard_aggrs: + LOG.warning('No aggregates found for host %(host)s.', + {'host': host_state.host}) + return 0 + + if len(host_shard_aggrs) > 1: + LOG.warning('More than one host aggregates found for ' + 'host %(host)s, selecting first.', + {'host': host_state.host}) + host_shard_aggr = host_shard_aggrs[0] + + group_hosts = None + if request_spec.instance_group and request_spec.instance_group.hosts: + group_hosts = set(request_spec.instance_group.hosts) + if not group_hosts: + return 0 + # group_hosts doesn't contain our host, because otherwise we would + # have returned a weighed already as we would have instances + if group_hosts & set(host_shard_aggr.hosts): + return 0.5 + return 0 + class ServerGroupSoftAntiAffinityWeigher(_SoftAffinityWeigherBase): policy_name = 'soft-anti-affinity' diff --git a/nova/tests/unit/scheduler/weights/test_weights_affinity.py b/nova/tests/unit/scheduler/weights/test_weights_affinity.py index 6f7ea7f5ff5..7ba7fea4032 100644 --- a/nova/tests/unit/scheduler/weights/test_weights_affinity.py +++ b/nova/tests/unit/scheduler/weights/test_weights_affinity.py @@ -29,7 +29,8 @@ def setUp(self): self.weight_handler = weights.HostWeightHandler() self.weighers = [] - def _get_weighed_host(self, hosts, policy, group='default'): + def _get_weighed_host(self, hosts, policy, group='default', + expected_host=None, match_host=False): if group == 'default': members = ['member1', 'member2', 'member3', 'member4', 'member5', 'member6', 'member7'] @@ -38,17 +39,27 @@ def _get_weighed_host(self, hosts, policy, group='default'): request_spec = objects.RequestSpec( instance_group=objects.InstanceGroup( policy=policy, - members=members)) - return self.weight_handler.get_weighed_objects(self.weighers, - hosts, - request_spec)[0] + members=members, + hosts=[h.host for h in hosts])) + hosts = self.weight_handler.get_weighed_objects(self.weighers, + hosts, + request_spec) + if not match_host: + return hosts[0] + else: + for host in hosts: + if host.obj.host == expected_host: + return host def _get_all_hosts(self): + aggs1 = [objects.Aggregate(id=1, name='vc-a-1', hosts=['host1'])] + aggs2 = [objects.Aggregate(id=2, name='vc-a-2', hosts=['host3']), + objects.Aggregate(id=2, name='vc-a-3', hosts=['host4'])] host_values = [ ('host1', 'node1', {'instances': { 'member1': mock.sentinel, 'instance13': mock.sentinel - }}), + }, 'aggregates': aggs1}), ('host2', 'node2', {'instances': { 'member2': mock.sentinel, 'member3': mock.sentinel, @@ -60,12 +71,12 @@ def _get_all_hosts(self): }}), ('host3', 'node3', {'instances': { 'instance15': mock.sentinel - }}), + }, 'aggregates': aggs2}), ('host4', 'node4', {'instances': { 'member6': mock.sentinel, 'member7': mock.sentinel, 'instance16': mock.sentinel - }})] + }, 'aggregates': aggs2})] return [fakes.FakeHostState(host, node, values) for host, node, values in host_values] @@ -105,6 +116,20 @@ def test_soft_affinity_weight_multiplier_positive_value(self): expected_weight=2.0, expected_host='host2') + def test_soft_affinity_weight_multiplier_same_shards(self): + """For host, which does not contain servers of server-group, + but in same shard as the servers in the server-group, weight + is 0.5. Due to normalization smallest weight become 0.0 + """ + self.flags(soft_affinity_weight_multiplier=2.0, + group='filter_scheduler') + expected_weight = 0.0 + hostinfo_list = self._get_all_hosts() + weighed_host = self._get_weighed_host(hostinfo_list, + policy='soft-affinity', group='default', + expected_host='host3', match_host=True) + self.assertEqual(expected_weight, weighed_host.weight) + @mock.patch.object(affinity, 'LOG') def test_soft_affinity_weight_multiplier_negative_value(self, mock_log): self.flags(soft_affinity_weight_multiplier=-1.0, @@ -116,7 +141,8 @@ def test_soft_affinity_weight_multiplier_negative_value(self, mock_log): self._do_test(policy='soft-affinity', expected_weight=0.0, expected_host='host3') - self.assertEqual(1, mock_log.warning.call_count) + # one from _weigh_object() and two from weight_multiplier() + self.assertEqual(3, mock_log.warning.call_count) def test_running_twice(self): """Run the weighing twice for different groups each run