Merge "Normalize the weights instead of using raw values"

This commit is contained in:
Jenkins
2013-12-16 20:14:16 +00:00
committed by Gerrit Code Review
11 changed files with 242 additions and 55 deletions

View File

@@ -263,27 +263,35 @@ default when no filters are specified in the request.
Weights
-------
Filter Scheduler uses so-called **weights** during its work.
Filter Scheduler uses the so called **weights** during its work. A weigher is a
way to select the best suitable host from a group of valid hosts by giving
weights to all the hosts in the list.
The Filter Scheduler weights hosts based on the config option
In order to prioritize one weigher against another, all the weighers have to
define a multiplier that will be applied before computing the weight for a node.
All the weights are normalized beforehand so that the multiplier can be applied
easily. Therefore the final weight for the object will be::
weight = w1_multiplier * norm(w1) + w2_multiplier * norm(w2) + ...
A weigher should be a subclass of ``weights.BaseHostWeigher`` and they must
implement the ``weight_multiplier`` and ``weight_object`` methods. If the
``weight_objects`` method is overriden it just return a list of weights, and not
modify the weight of the object directly, since final weights are normalized and
computed by ``weight.BaseWeightHandler``.
The Filter Scheduler weighs hosts based on the config option
`scheduler_weight_classes`, this defaults to
`nova.scheduler.weights.all_weighers`, which selects all the available weighers
in the package nova.scheduler.weights. Hosts are then weighted and sorted with
the largest weight winning. For each host, the final weight is calculated by
summing up all weigher's weight value multiplying its own weight_mutiplier:
`nova.scheduler.weights.all_weighers`, which selects the following weighers:
::
* |RamWeigher| Hosts are then weighted and sorted with the largest weight winning.
If the multiplier is negative, the host with less RAM available will win (useful
for stacking hosts, instead of spreading).
* |MetricsWeigher| This weigher can compute the weight based on the compute node
host's various metrics. The to-be weighed metrics and their weighing ratio
are specified in the configuration file as the followings::
final_weight = 0
for each weigher:
final_weight += weigher's weight_mutiplier * weigher's calculated weight value
The weigher's weight_mutiplier can be set in the configuration file, e.g.
::
[metrics]
weight_multiplier=1.0
metrics_weight_setting = name1=1.0, name2=-1.0
Filter Scheduler finds local list of acceptable hosts by repeated filtering and
weighing. Each time it chooses a host, it virtually consumes resources on it,
@@ -322,3 +330,5 @@ in :mod:``nova.tests.scheduler``.
.. |AggregateTypeAffinityFilter| replace:: :class:`AggregateTypeAffinityFilter <nova.scheduler.filters.type_filter.AggregateTypeAffinityFilter>`
.. |AggregateInstanceExtraSpecsFilter| replace:: :class:`AggregateInstanceExtraSpecsFilter <nova.scheduler.filters.aggregate_instance_extra_specs.AggregateInstanceExtraSpecsFilter>`
.. |AggregateMultiTenancyIsolation| replace:: :class:`AggregateMultiTenancyIsolation <nova.scheduler.filters.aggregate_multitenancy_isolation.AggregateMultiTenancyIsolation>`
.. |RamWeigher| replace:: :class:`RamWeigher <nova.scheduler.weights.all_weighers.RamWeigher>`

View File

@@ -3060,6 +3060,15 @@
#ram_weight_multiplier=10.0
#
# Options defined in nova.cells.weights.weight_offset
#
# Multiplier used to weigh offset weigher. (floating point
# value)
#offset_weight_multiplier=1.0
[baremetal]
#

View File

@@ -48,7 +48,7 @@ class MuteChildWeigher(weights.BaseCellWeigher):
weight.
"""
def _weight_multiplier(self):
def weight_multiplier(self):
# negative multiplier => lower weight
return CONF.cells.mute_weight_multiplier

View File

@@ -34,7 +34,7 @@ CONF.register_opts(ram_weigher_opts, group='cells')
class RamByInstanceTypeWeigher(weights.BaseCellWeigher):
"""Weigh cells by instance_type requested."""
def _weight_multiplier(self):
def weight_multiplier(self):
return CONF.cells.ram_weight_multiplier
def _weigh_object(self, cell, weight_properties):

View File

@@ -18,8 +18,19 @@ Weigh cells by their weight_offset in the DB. Cells with higher
weight_offsets in the DB will be preferred.
"""
from oslo.config import cfg
from nova.cells import weights
weigher_opts = [
cfg.FloatOpt('offset_weight_multiplier',
default=1.0,
help='Multiplier used to weigh offset weigher.'),
]
CONF = cfg.CONF
CONF.register_opts(weigher_opts, group='cells')
class WeightOffsetWeigher(weights.BaseCellWeigher):
"""
@@ -28,6 +39,9 @@ class WeightOffsetWeigher(weights.BaseCellWeigher):
its weight_offset to 999999999999999 (highest weight wins)
"""
def weight_multiplier(self):
return CONF.cells.offset_weight_multiplier
def _weigh_object(self, cell, weight_properties):
"""Returns whatever was in the DB for weight_offset."""
return cell.db_info.get('weight_offset', 0)

View File

@@ -76,7 +76,7 @@ class MetricsWeigher(weights.BaseHostWeigher):
" metrics_weight_setting: %s"),
",".join(bad))
def _weight_multiplier(self):
def weight_multiplier(self):
"""Override the weight multiplier."""
return CONF.metrics.weight_multiplier

View File

@@ -36,7 +36,9 @@ CONF.register_opts(ram_weight_opts)
class RAMWeigher(weights.BaseHostWeigher):
def _weight_multiplier(self):
minval = 0
def weight_multiplier(self):
"""Override the weight multiplier."""
return CONF.ram_weight_multiplier

View File

@@ -213,5 +213,5 @@ class MuteWeigherTestClass(_WeigherTestClass):
for i in range(2):
weighed_cell = weighed_cells.pop(0)
self.assertEqual(1000 * -10.0, weighed_cell.weight)
self.assertEqual(-10.0, weighed_cell.weight)
self.assertIn(weighed_cell.obj.name, ['cell1', 'cell2'])

View File

@@ -72,37 +72,62 @@ class RamWeigherTestCase(test.NoDBTestCase):
# so, host4 should win:
weighed_host = self._get_weighed_host(hostinfo_list)
self.assertEqual(weighed_host.weight, 8192)
self.assertEqual(weighed_host.weight, 1.0)
self.assertEqual(weighed_host.obj.host, 'host4')
def test_ram_filter_multiplier1(self):
self.flags(ram_weight_multiplier=-1.0)
self.flags(ram_weight_multiplier=0.0)
hostinfo_list = self._get_all_hosts()
# host1: free_ram_mb=-512
# host2: free_ram_mb=-1024
# host3: free_ram_mb=-3072
# host4: free_ram_mb=-8192
# host1: free_ram_mb=512
# host2: free_ram_mb=1024
# host3: free_ram_mb=3072
# host4: free_ram_mb=8192
# so, host1 should win:
# We do not know the host, all have same weight.
weighed_host = self._get_weighed_host(hostinfo_list)
self.assertEqual(weighed_host.weight, -512)
self.assertEqual(weighed_host.obj.host, 'host1')
self.assertEqual(weighed_host.weight, 0.0)
def test_ram_filter_multiplier2(self):
self.flags(ram_weight_multiplier=2.0)
hostinfo_list = self._get_all_hosts()
# host1: free_ram_mb=512 * 2
# host2: free_ram_mb=1024 * 2
# host3: free_ram_mb=3072 * 2
# host4: free_ram_mb=8192 * 2
# host1: free_ram_mb=512
# host2: free_ram_mb=1024
# host3: free_ram_mb=3072
# host4: free_ram_mb=8192
# so, host4 should win:
weighed_host = self._get_weighed_host(hostinfo_list)
self.assertEqual(weighed_host.weight, 8192 * 2)
self.assertEqual(weighed_host.weight, 1.0 * 2)
self.assertEqual(weighed_host.obj.host, 'host4')
def test_ram_filter_negative(self):
self.flags(ram_weight_multiplier=1.0)
hostinfo_list = self._get_all_hosts()
host_attr = {'id': 100, 'memory_mb': 8192, 'free_ram_mb': -512}
host_state = fakes.FakeHostState('negative', 'negative', host_attr)
hostinfo_list = list(hostinfo_list) + [host_state]
# host1: free_ram_mb=512
# host2: free_ram_mb=1024
# host3: free_ram_mb=3072
# host4: free_ram_mb=8192
# negativehost: free_ram_mb=-512
# so, host4 should win
weights = self.weight_handler.get_weighed_objects(self.weight_classes,
hostinfo_list, {})
weighed_host = weights[0]
self.assertEqual(weighed_host.weight, 1)
self.assertEqual(weighed_host.obj.host, "host4")
# and negativehost should lose
weighed_host = weights[-1]
self.assertEqual(weighed_host.weight, 0)
self.assertEqual(weighed_host.obj.host, "negative")
class MetricsWeigherTestCase(test.NoDBTestCase):
def setUp(self):
@@ -139,7 +164,7 @@ class MetricsWeigherTestCase(test.NoDBTestCase):
# host4: foo=8192
# so, host4 should win:
setting = ['foo=1']
self._do_test(setting, 8192, 'host4')
self._do_test(setting, 1.0, 'host4')
def test_multiple_resource(self):
# host1: foo=512, bar=1
@@ -148,7 +173,7 @@ class MetricsWeigherTestCase(test.NoDBTestCase):
# host4: foo=8192, bar=0
# so, host2 should win:
setting = ['foo=0.0001', 'bar=1']
self._do_test(setting, 2.1024, 'host2')
self._do_test(setting, 1.0, 'host2')
def test_single_resourcenegtive_ratio(self):
# host1: foo=512
@@ -157,7 +182,7 @@ class MetricsWeigherTestCase(test.NoDBTestCase):
# host4: foo=8192
# so, host1 should win:
setting = ['foo=-1']
self._do_test(setting, -512, 'host1')
self._do_test(setting, 1.0, 'host1')
def test_multiple_resource_missing_ratio(self):
# host1: foo=512, bar=1
@@ -166,7 +191,7 @@ class MetricsWeigherTestCase(test.NoDBTestCase):
# host4: foo=8192, bar=0
# so, host4 should win:
setting = ['foo=0.0001', 'bar']
self._do_test(setting, 0.8192, 'host4')
self._do_test(setting, 1.0, 'host4')
def test_multiple_resource_wrong_ratio(self):
# host1: foo=512, bar=1
@@ -175,7 +200,7 @@ class MetricsWeigherTestCase(test.NoDBTestCase):
# host4: foo=8192, bar=0
# so, host4 should win:
setting = ['foo=0.0001', 'bar = 2.0t']
self._do_test(setting, 0.8192, 'host4')
self._do_test(setting, 1.0, 'host4')
def _check_parsing_result(self, weigher, setting, results):
self.flags(weight_setting=setting, group='metrics')

View File

@@ -0,0 +1,54 @@
# Copyright 2011-2012 OpenStack Foundation
# 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.
"""
Tests For weights.
"""
from nova import test
from nova import weights
class TestWeigher(test.NoDBTestCase):
def test_no_multiplier(self):
class FakeWeigher(weights.BaseWeigher):
def _weigh_object(self, *args, **kwargs):
pass
self.assertEqual(1.0,
FakeWeigher().weight_multiplier())
def test_no_weight_object(self):
class FakeWeigher(weights.BaseWeigher):
def weight_multiplier(self, *args, **kwargs):
pass
self.assertRaises(TypeError,
FakeWeigher)
def test_normalization(self):
# weight_list, expected_result, minval, maxval
map_ = (
((), (), None, None),
((0.0, 0.0), (0.0, 0.0), None, None),
((1.0, 1.0), (0.0, 0.0), None, None),
((20.0, 50.0), (0.0, 1.0), None, None),
((20.0, 50.0), (0.0, 0.375), None, 100.0),
((20.0, 50.0), (0.4, 1.0), 0.0, None),
((20.0, 50.0), (0.2, 0.5), 0.0, 100.0),
)
normalize_to = (1.0, 10.0)
for seq, result, minval, maxval in map_:
ret = weights.normalize(seq, minval=minval, maxval=maxval)
self.assertEqual(tuple(ret), result)

View File

@@ -17,9 +17,40 @@
Pluggable Weighing support
"""
import abc
from nova import loadables
def normalize(weight_list, minval=None, maxval=None):
"""Normalize the values in a list between 0 and 1.0.
The normalization is made regarding the lower and upper values present in
weight_list. If the minval and/or maxval parameters are set, these values
will be used instead of the minimum and maximum from the list.
If all the values are equal, they are normalized to 0.
"""
if not weight_list:
return ()
if maxval is None:
maxval = max(weight_list)
if minval is None:
minval = min(weight_list)
maxval = float(maxval)
minval = float(minval)
if minval == maxval:
return [0] * len(weight_list)
range_ = maxval - minval
return ((i - minval) / range_ for i in weight_list)
class WeighedObject(object):
"""Object with weight information."""
def __init__(self, obj, weight):
@@ -31,26 +62,59 @@ class WeighedObject(object):
class BaseWeigher(object):
"""Base class for pluggable weighers."""
def _weight_multiplier(self):
"""How weighted this weigher should be. Normally this would
be overridden in a subclass based on a config value.
"""Base class for pluggable weighers.
The attributes maxval and minval can be specified to set up the maximum
and minimum values for the weighed objects. These values will then be
taken into account in the normalization step, instead of taking the values
from the calculated weights.
"""
__metaclass__ = abc.ABCMeta
minval = None
maxval = None
def weight_multiplier(self):
"""How weighted this weigher should be.
Override this method in a subclass, so that the returned value is
read from a configuration option to permit operators specify a
multiplier for the weigher.
"""
return 1.0
@abc.abstractmethod
def _weigh_object(self, obj, weight_properties):
"""Override in a subclass to specify a weight for a specific
object.
"""
return 0.0
"""Weigh an specific object."""
def weigh_objects(self, weighed_obj_list, weight_properties):
"""Weigh multiple objects. Override in a subclass if you need
need access to all objects in order to manipulate weights.
"""Weigh multiple objects.
Override in a subclass if you need access to all objects in order
to calculate weights. Do not modify the weight of an object here,
just return a list of weights.
"""
# Calculate the weights
weights = []
for obj in weighed_obj_list:
obj.weight += (self._weight_multiplier() *
self._weigh_object(obj.obj, weight_properties))
weight = self._weigh_object(obj.obj, weight_properties)
# Record the min and max values if they are None. If they anything
# but none we assume that the weigher has set them
if self.minval is None:
self.minval = weight
if self.maxval is None:
self.maxval = weight
if weight < self.minval:
self.minval = weight
elif weight > self.maxval:
self.maxval = weight
weights.append(weight)
return weights
class BaseWeightHandler(loadables.BaseLoader):
@@ -58,7 +122,7 @@ class BaseWeightHandler(loadables.BaseLoader):
def get_weighed_objects(self, weigher_classes, obj_list,
weighing_properties):
"""Return a sorted (highest score first) list of WeighedObjects."""
"""Return a sorted (descending), normalized list of WeighedObjects."""
if not obj_list:
return []
@@ -66,6 +130,15 @@ class BaseWeightHandler(loadables.BaseLoader):
weighed_objs = [self.object_class(obj, 0.0) for obj in obj_list]
for weigher_cls in weigher_classes:
weigher = weigher_cls()
weigher.weigh_objects(weighed_objs, weighing_properties)
weights = weigher.weigh_objects(weighed_objs, weighing_properties)
# Normalize the weights
weights = normalize(weights,
minval=weigher.minval,
maxval=weigher.maxval)
for i, weight in enumerate(weights):
obj = weighed_objs[i]
obj.weight += weigher.weight_multiplier() * weight
return sorted(weighed_objs, key=lambda x: x.weight, reverse=True)