scheduler: add soft-(anti-)affinity weighers
ServerGroupSoftAffinityWeigher implements the soft-affinity policy for server groups as it orders the hosts by the number of instances running on them from the same group in decreasing order. ServerGroupSoftAntiAffinityWeigher implements the soft-anti-affinity policy for server groups as it orders the hosts by the number of instances running on them from the same group in increasing order. Both weigher assumes the the RequestSpec object refers to a valid InstanceGroup object which has a populated members field. This will be provided by the subsequent patch. New configuration options added: * soft_affinity_weight_multiplier * soft_anti_affinity_multiplier Add docs about the new weigher classes and change the function name 'weigh_object' to '_weigh_object' in the Weights section to match the code. DocImpact Co-Authored-By: Balazs Gibizer <balazs.gibizer@ericsson.com> Implements: blueprint soft-affinity-for-server-group Change-Id: I3f156d5e5df4d9642bb4b0ffac30a6288459ce61changes/16/147516/59
parent
5af634f00a
commit
72ba18468e
@ -0,0 +1,90 @@
|
||||
# Copyright (c) 2015 Ericsson AB
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
"""
|
||||
Affinity Weighers. Weigh hosts by the number of instances from a given host.
|
||||
|
||||
AffinityWeigher implements the soft-affinity policy for server groups by
|
||||
preferring the hosts that has more instances from the given group.
|
||||
|
||||
AntiAffinityWeigher implements the soft-anti-affinity policy for server groups
|
||||
by preferring the hosts that has less instances from the given group.
|
||||
|
||||
"""
|
||||
from oslo_config import cfg
|
||||
from oslo_log import log as logging
|
||||
|
||||
from nova.i18n import _LW
|
||||
from nova.scheduler import weights
|
||||
|
||||
CONF = cfg.CONF
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class _SoftAffinityWeigherBase(weights.BaseHostWeigher):
|
||||
policy_name = None
|
||||
|
||||
def _weigh_object(self, host_state, request_spec):
|
||||
"""Higher weights win."""
|
||||
if not request_spec.instance_group:
|
||||
return 0
|
||||
|
||||
policies = request_spec.instance_group.policies
|
||||
|
||||
if self.policy_name not in policies:
|
||||
return 0
|
||||
|
||||
instances = set(host_state.instances.keys())
|
||||
members = set(request_spec.instance_group.members)
|
||||
member_on_host = instances.intersection(members)
|
||||
|
||||
return len(member_on_host)
|
||||
|
||||
|
||||
class ServerGroupSoftAffinityWeigher(_SoftAffinityWeigherBase):
|
||||
policy_name = 'soft-affinity'
|
||||
warning_sent = False
|
||||
|
||||
def weight_multiplier(self):
|
||||
if (CONF.soft_affinity_weight_multiplier < 0 and
|
||||
not self.warning_sent):
|
||||
LOG.warn(_LW('For the soft_affinity_weight_multiplier only a '
|
||||
'positive value is meaningful as a negative value '
|
||||
'would mean that the affinity weigher would '
|
||||
'prefer non-collocating placement.'))
|
||||
self.warning_sent = True
|
||||
|
||||
return CONF.soft_affinity_weight_multiplier
|
||||
|
||||
|
||||
class ServerGroupSoftAntiAffinityWeigher(_SoftAffinityWeigherBase):
|
||||
policy_name = 'soft-anti-affinity'
|
||||
warning_sent = False
|
||||
|
||||
def weight_multiplier(self):
|
||||
if (CONF.soft_anti_affinity_weight_multiplier < 0 and
|
||||
not self.warning_sent):
|
||||
LOG.warn(_LW('For the soft_anti_affinity_weight_multiplier only a '
|
||||
'positive value is meaningful as a negative value '
|
||||
'would mean that the anti-affinity weigher would '
|
||||
'prefer collocating placement.'))
|
||||
self.warning_sent = True
|
||||
|
||||
return CONF.soft_anti_affinity_weight_multiplier
|
||||
|
||||
def _weigh_object(self, host_state, request_spec):
|
||||
weight = super(ServerGroupSoftAntiAffinityWeigher, self)._weigh_object(
|
||||
host_state, request_spec)
|
||||
return -1 * weight
|
@ -0,0 +1,153 @@
|
||||
# Copyright (c) 2015 Ericsson AB
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import mock
|
||||
|
||||
from nova import objects
|
||||
from nova.scheduler import weights
|
||||
from nova.scheduler.weights import affinity
|
||||
from nova import test
|
||||
from nova.tests.unit.scheduler import fakes
|
||||
|
||||
|
||||
class SoftWeigherTestBase(test.NoDBTestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(SoftWeigherTestBase, self).setUp()
|
||||
self.weight_handler = weights.HostWeightHandler()
|
||||
self.weighers = []
|
||||
|
||||
def _get_weighed_host(self, hosts, policy):
|
||||
request_spec = objects.RequestSpec(
|
||||
instance_group=objects.InstanceGroup(
|
||||
policies=[policy],
|
||||
members=['member1',
|
||||
'member2',
|
||||
'member3',
|
||||
'member4',
|
||||
'member5',
|
||||
'member6',
|
||||
'member7']))
|
||||
return self.weight_handler.get_weighed_objects(self.weighers,
|
||||
hosts,
|
||||
request_spec)[0]
|
||||
|
||||
def _get_all_hosts(self):
|
||||
host_values = [
|
||||
('host1', 'node1', {'instances': {
|
||||
'member1': mock.sentinel,
|
||||
'instance13': mock.sentinel
|
||||
}}),
|
||||
('host2', 'node2', {'instances': {
|
||||
'member2': mock.sentinel,
|
||||
'member3': mock.sentinel,
|
||||
'member4': mock.sentinel,
|
||||
'member5': mock.sentinel,
|
||||
'instance14': mock.sentinel
|
||||
}}),
|
||||
('host3', 'node3', {'instances': {
|
||||
'instance15': mock.sentinel
|
||||
}}),
|
||||
('host4', 'node4', {'instances': {
|
||||
'member6': mock.sentinel,
|
||||
'member7': mock.sentinel,
|
||||
'instance16': mock.sentinel
|
||||
}})]
|
||||
return [fakes.FakeHostState(host, node, values)
|
||||
for host, node, values in host_values]
|
||||
|
||||
def _do_test(self, policy, expected_weight,
|
||||
expected_host):
|
||||
hostinfo_list = self._get_all_hosts()
|
||||
weighed_host = self._get_weighed_host(hostinfo_list,
|
||||
policy)
|
||||
self.assertEqual(expected_weight, weighed_host.weight)
|
||||
if expected_host:
|
||||
self.assertEqual(expected_host, weighed_host.obj.host)
|
||||
|
||||
|
||||
class SoftAffinityWeigherTestCase(SoftWeigherTestBase):
|
||||
|
||||
def setUp(self):
|
||||
super(SoftAffinityWeigherTestCase, self).setUp()
|
||||
self.weighers = [affinity.ServerGroupSoftAffinityWeigher()]
|
||||
|
||||
def test_soft_affinity_weight_multiplier_by_default(self):
|
||||
self._do_test(policy='soft-affinity',
|
||||
expected_weight=1.0,
|
||||
expected_host='host2')
|
||||
|
||||
def test_soft_affinity_weight_multiplier_zero_value(self):
|
||||
# We do not know the host, all have same weight.
|
||||
self.flags(soft_affinity_weight_multiplier=0.0)
|
||||
self._do_test(policy='soft-affinity',
|
||||
expected_weight=0.0,
|
||||
expected_host=None)
|
||||
|
||||
def test_soft_affinity_weight_multiplier_positive_value(self):
|
||||
self.flags(soft_affinity_weight_multiplier=2.0)
|
||||
self._do_test(policy='soft-affinity',
|
||||
expected_weight=2.0,
|
||||
expected_host='host2')
|
||||
|
||||
@mock.patch.object(affinity, 'LOG')
|
||||
def test_soft_affinity_weight_multiplier_negative_value(self, mock_log):
|
||||
self.flags(soft_affinity_weight_multiplier=-1.0)
|
||||
self._do_test(policy='soft-affinity',
|
||||
expected_weight=0.0,
|
||||
expected_host='host3')
|
||||
# call twice and assert that only one warning is emitted
|
||||
self._do_test(policy='soft-affinity',
|
||||
expected_weight=0.0,
|
||||
expected_host='host3')
|
||||
self.assertEqual(1, mock_log.warn.call_count)
|
||||
|
||||
|
||||
class SoftAntiAffinityWeigherTestCase(SoftWeigherTestBase):
|
||||
|
||||
def setUp(self):
|
||||
super(SoftAntiAffinityWeigherTestCase, self).setUp()
|
||||
self.weighers = [affinity.ServerGroupSoftAntiAffinityWeigher()]
|
||||
|
||||
def test_soft_anti_affinity_weight_multiplier_by_default(self):
|
||||
self._do_test(policy='soft-anti-affinity',
|
||||
expected_weight=1.0,
|
||||
expected_host='host3')
|
||||
|
||||
def test_soft_anti_affinity_weight_multiplier_zero_value(self):
|
||||
# We do not know the host, all have same weight.
|
||||
self.flags(soft_anti_affinity_weight_multiplier=0.0)
|
||||
self._do_test(policy='soft-anti-affinity',
|
||||
expected_weight=0.0,
|
||||
expected_host=None)
|
||||
|
||||
def test_soft_anti_affinity_weight_multiplier_positive_value(self):
|
||||
self.flags(soft_anti_affinity_weight_multiplier=2.0)
|
||||
self._do_test(policy='soft-anti-affinity',
|
||||
expected_weight=2.0,
|
||||
expected_host='host3')
|
||||
|
||||
@mock.patch.object(affinity, 'LOG')
|
||||
def test_soft_anti_affinity_weight_multiplier_negative_value(self,
|
||||
mock_log):
|
||||
self.flags(soft_anti_affinity_weight_multiplier=-1.0)
|
||||
self._do_test(policy='soft-anti-affinity',
|
||||
expected_weight=0.0,
|
||||
expected_host='host2')
|
||||
# call twice and assert that only one warning is emitted
|
||||
self._do_test(policy='soft-anti-affinity',
|
||||
expected_weight=0.0,
|
||||
expected_host='host2')
|
||||
self.assertEqual(1, mock_log.warn.call_count)
|
Loading…
Reference in New Issue