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: I3f156d5e5df4d9642bb4b0ffac30a6288459ce61
This commit is contained in:
Ildiko Vancsa 2015-01-06 14:40:33 +01:00 committed by Balazs Gibizer
parent 5af634f00a
commit 72ba18468e
5 changed files with 280 additions and 0 deletions

View File

@ -390,6 +390,19 @@ The Filter Scheduler weighs hosts based on the config option
hosts. If the multiplier is positive, the weigher prefer choosing heavy
workload compute hosts, the weighing has the opposite effect of the default.
* |ServerGroupSoftAffinityWeigher| The weigher can compute the weight based
on the number of instances that run on the same server group. The largest
weight defines the preferred host for the new instance. For the multiplier
only a positive value is meaningful for the calculation as a negative value
would mean that the affinity weigher would prefer non collocating placement.
* |ServerGroupSoftAntiAffinityWeigher| The weigher can compute the weight based
on the number of instances that run on the same server group as a negative
value. The largest weight defines the preferred host for the new instance.
For the multiplier only a positive value is meaningful for the calculation as
a negative value would mean that the anti-affinity weigher would prefer
collocating placement.
Filter Scheduler makes a local list of acceptable hosts by repeated filtering and
weighing. Each time it chooses a host, it virtually consumes resources on it,
so subsequent selections can adjust accordingly. It is useful if the customer
@ -440,3 +453,5 @@ in :mod:`nova.tests.scheduler`.
.. |MetricsFilter| replace:: :class:`MetricsFilter <nova.scheduler.filters.metrics_filter.MetricsFilter>`
.. |MetricsWeigher| replace:: :class:`MetricsWeigher <nova.scheduler.weights.metrics.MetricsWeigher>`
.. |IoOpsWeigher| replace:: :class:`IoOpsWeigher <nova.scheduler.weights.io_ops.IoOpsWeigher>`
.. |ServerGroupSoftAffinityWeigher| replace:: :class:`ServerGroupSoftAffinityWeigher <nova.scheduler.weights.affinity.ServerGroupSoftAffinityWeigher>`
.. |ServerGroupSoftAntiAffinityWeigher| replace:: :class:`ServerGroupSoftAntiAffinityWeigher <nova.scheduler.weights.affinity.ServerGroupSoftAntiAffinityWeigher>`

View File

@ -208,6 +208,23 @@ scheduler_max_att_opt = cfg.IntOpt("scheduler_max_attempts",
default=3,
help="Maximum number of attempts to schedule an instance")
soft_affinity_weight_opt = cfg.FloatOpt('soft_affinity_weight_multiplier',
default=1.0,
help='Multiplier used for weighing hosts '
'for group soft-affinity. Only a '
'positive value is meaningful. Negative '
'means that the behavior will change to '
'the opposite, which is soft-anti-affinity.')
soft_anti_affinity_weight_opt = cfg.FloatOpt(
'soft_anti_affinity_weight_multiplier',
default=1.0,
help='Multiplier used for weighing hosts '
'for group soft-anti-affinity. Only a '
'positive value is meaningful. Negative '
'means that the behavior will change to '
'the opposite, which is soft-affinity.')
SIMPLE_OPTS = [host_subset_size_opt,
bm_default_filter_opt,
@ -232,6 +249,8 @@ SIMPLE_OPTS = [host_subset_size_opt,
ram_weight_mult_opt,
io_ops_weight_mult_opt,
scheduler_max_att_opt,
soft_affinity_weight_opt,
soft_anti_affinity_weight_opt,
]
ALL_OPTS = itertools.chain(

View File

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

View File

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

View File

@ -17,6 +17,7 @@ Tests For Scheduler weights.
"""
from nova.scheduler import weights
from nova.scheduler.weights import affinity
from nova.scheduler.weights import io_ops
from nova.scheduler.weights import metrics
from nova.scheduler.weights import ram
@ -38,3 +39,5 @@ class TestWeighedHost(test.NoDBTestCase):
self.assertIn(ram.RAMWeigher, classes)
self.assertIn(metrics.MetricsWeigher, classes)
self.assertIn(io_ops.IoOpsWeigher, classes)
self.assertIn(affinity.ServerGroupSoftAffinityWeigher, classes)
self.assertIn(affinity.ServerGroupSoftAntiAffinityWeigher, classes)