diff --git a/nova/scheduler/least_cost.py b/nova/scheduler/least_cost.py deleted file mode 100644 index d3eaee73..00000000 --- a/nova/scheduler/least_cost.py +++ /dev/null @@ -1,118 +0,0 @@ -# Copyright (c) 2011 OpenStack, LLC. -# 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. -""" -Least Cost is an algorithm for choosing which host machines to -provision a set of resources to. The input is a WeightedHost object which -is decided upon by a set of objective-functions, called the 'cost-functions'. -The WeightedHost contains a combined weight for each cost-function. - -The cost-function and weights are tabulated, and the host with the least cost -is then selected for provisioning. -""" - -from nova import config -from nova import flags -from nova.openstack.common import cfg -from nova.openstack.common import log as logging - - -LOG = logging.getLogger(__name__) - -least_cost_opts = [ - cfg.ListOpt('least_cost_functions', - default=[ - 'nova.scheduler.least_cost.compute_fill_first_cost_fn' - ], - help='Which cost functions the LeastCostScheduler should use'), - cfg.FloatOpt('noop_cost_fn_weight', - default=1.0, - help='How much weight to give the noop cost function'), - cfg.FloatOpt('compute_fill_first_cost_fn_weight', - default=-1.0, - help='How much weight to give the fill-first cost function. ' - 'A negative value will reverse behavior: ' - 'e.g. spread-first'), - ] - -CONF = config.CONF -CONF.register_opts(least_cost_opts) - -# TODO(sirp): Once we have enough of these rules, we can break them out into a -# cost_functions.py file (perhaps in a least_cost_scheduler directory) - - -class WeightedHost(object): - """Reduced set of information about a host that has been weighed. - This is an attempt to remove some of the ad-hoc dict structures - previously used.""" - - def __init__(self, weight, host_state=None): - self.weight = weight - self.host_state = host_state - - def to_dict(self): - x = dict(weight=self.weight) - if self.host_state: - x['host'] = self.host_state.host - return x - - def __repr__(self): - if self.host_state: - return "WeightedHost host: %s" % self.host_state.host - return "WeightedHost with no host_state" - - -def noop_cost_fn(host_state, weighing_properties): - """Return a pre-weight cost of 1 for each host""" - return 1 - - -def compute_fill_first_cost_fn(host_state, weighing_properties): - """More free ram = higher weight. So servers with less free - ram will be preferred. - - Note: the weight for this function in default configuration - is -1.0. With a -1.0 this function runs in reverse, so systems - with the most free memory will be preferred. - """ - return host_state.free_ram_mb - - -def weighted_sum(weighted_fns, host_states, weighing_properties): - """Use the weighted-sum method to compute a score for an array of objects. - - Normalize the results of the objective-functions so that the weights are - meaningful regardless of objective-function's range. - - :param host_list: ``[(host, HostInfo()), ...]`` - :param weighted_fns: list of weights and functions like:: - - [(weight, objective-functions), ...] - - :param weighing_properties: an arbitrary dict of values that can - influence weights. - - :returns: a single WeightedHost object which represents the best - candidate. - """ - - min_score, best_host = None, None - for host_state in host_states: - score = sum(weight * fn(host_state, weighing_properties) - for weight, fn in weighted_fns) - if min_score is None or score < min_score: - min_score, best_host = score, host_state - - return WeightedHost(min_score, host_state=best_host) diff --git a/nova/tests/scheduler/test_least_cost.py b/nova/tests/scheduler/test_least_cost.py index 1d180d71..f8ed20b4 100644 --- a/nova/tests/scheduler/test_least_cost.py +++ b/nova/tests/scheduler/test_least_cost.py @@ -1,4 +1,4 @@ -# Copyright 2011 OpenStack LLC. +# Copyright 2011-2012 OpenStack LLC. # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may @@ -15,27 +15,51 @@ """ Tests For Least Cost functions. """ +from nova import config from nova import context -from nova.scheduler import host_manager -from nova.scheduler import least_cost +from nova.openstack.common import cfg +from nova.scheduler import weights +from nova.scheduler.weights import least_cost from nova import test -from nova.tests import matchers from nova.tests.scheduler import fakes -def offset(hostinfo, options): +test_least_cost_opts = [ + cfg.FloatOpt('compute_fake_weigher1_weight', + default=2.0, + help='How much weight to give the fake_weigher1 function'), + cfg.FloatOpt('compute_fake_weigher2_weight', + default=1.0, + help='How much weight to give the fake_weigher2 function'), + ] + +CONF = config.CONF +CONF.import_opt('least_cost_functions', 'nova.scheduler.weights.least_cost') +CONF.import_opt('compute_fill_first_cost_fn_weight', + 'nova.scheduler.weights.least_cost') +CONF.register_opts(test_least_cost_opts) + + +def compute_fake_weigher1(hostinfo, options): return hostinfo.free_ram_mb + 10000 -def scale(hostinfo, options): +def compute_fake_weigher2(hostinfo, options): return hostinfo.free_ram_mb * 2 class LeastCostTestCase(test.TestCase): def setUp(self): super(LeastCostTestCase, self).setUp() - self.flags(reserved_host_disk_mb=0, reserved_host_memory_mb=0) self.host_manager = fakes.FakeHostManager() + self.weight_handler = weights.HostWeightHandler() + + def _get_weighed_host(self, hosts, weight_properties=None): + weigher_classes = least_cost.get_least_cost_weighers() + if weight_properties is None: + weight_properties = {} + return self.weight_handler.get_weighed_objects(weigher_classes, + hosts, weight_properties)[0] def _get_all_hosts(self): ctxt = context.get_admin_context() @@ -46,8 +70,39 @@ class LeastCostTestCase(test.TestCase): self.mox.ResetAll() return host_states - def test_weighted_sum_happy_day(self): - fn_tuples = [(1.0, offset), (1.0, scale)] + def test_default_of_spread_first(self): + # Default modifier is -1.0, so it turns out that hosts with + # the most free memory win + 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 + + # so, host1 should win: + weighed_host = self._get_weighed_host(hostinfo_list) + self.assertEqual(weighed_host.weight, 8192) + self.assertEqual(weighed_host.obj.host, 'host4') + + def test_filling_first(self): + self.flags(compute_fill_first_cost_fn_weight=1.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 + + # so, host1 should win: + weighed_host = self._get_weighed_host(hostinfo_list) + self.assertEqual(weighed_host.weight, -512) + self.assertEqual(weighed_host.obj.host, 'host1') + + def test_weighted_sum_provided_method(self): + fns = ['nova.tests.scheduler.test_least_cost.compute_fake_weigher1', + 'nova.tests.scheduler.test_least_cost.compute_fake_weigher2'] + self.flags(least_cost_functions=fns) hostinfo_list = self._get_all_hosts() # host1: free_ram_mb=512 @@ -59,18 +114,17 @@ class LeastCostTestCase(test.TestCase): # [10512, 11024, 13072, 18192] # [1024, 2048, 6144, 16384] - # adjusted [ 1.0 * x + 1.0 * y] = - # [11536, 13072, 19216, 34576] + # adjusted [ 2.0 * x + 1.0 * y] = + # [22048, 24096, 32288, 52768] # so, host1 should win: - options = {} - weighted_host = least_cost.weighted_sum(fn_tuples, hostinfo_list, - options) - self.assertEqual(weighted_host.weight, 11536) - self.assertEqual(weighted_host.host_state.host, 'host1') + weighed_host = self._get_weighed_host(hostinfo_list) + self.assertEqual(weighed_host.weight, 52768) + self.assertEqual(weighed_host.obj.host, 'host4') def test_weighted_sum_single_function(self): - fn_tuples = [(1.0, offset), ] + fns = ['nova.tests.scheduler.test_least_cost.compute_fake_weigher1'] + self.flags(least_cost_functions=fns) hostinfo_list = self._get_all_hosts() # host1: free_ram_mb=0 @@ -80,24 +134,10 @@ class LeastCostTestCase(test.TestCase): # [offset, ]= # [10512, 11024, 13072, 18192] + # adjusted [ 2.0 * x ]= + # [21024, 22048, 26144, 36384] # so, host1 should win: - options = {} - weighted_host = least_cost.weighted_sum(fn_tuples, hostinfo_list, - options) - self.assertEqual(weighted_host.weight, 10512) - self.assertEqual(weighted_host.host_state.host, 'host1') - - -class TestWeightedHost(test.TestCase): - def test_dict_conversion_without_host_state(self): - host = least_cost.WeightedHost('someweight') - expected = {'weight': 'someweight'} - self.assertThat(host.to_dict(), matchers.DictMatches(expected)) - - def test_dict_conversion_with_host_state(self): - host_state = host_manager.HostState('somehost', None) - host = least_cost.WeightedHost('someweight', host_state) - expected = {'weight': 'someweight', - 'host': 'somehost'} - self.assertThat(host.to_dict(), matchers.DictMatches(expected)) + weighed_host = self._get_weighed_host(hostinfo_list) + self.assertEqual(weighed_host.weight, 36384) + self.assertEqual(weighed_host.obj.host, 'host4')