1
# Copyright (c) 2011 Openstack, LLC.
4
# Licensed under the Apache License, Version 2.0 (the "License"); you may
5
# not use this file except in compliance with the License. You may obtain
6
# a copy of the License at
8
# http://www.apache.org/licenses/LICENSE-2.0
10
# Unless required by applicable law or agreed to in writing, software
11
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13
# License for the specific language governing permissions and limitations
16
Least Cost Scheduler is a mechanism for choosing which host machines to
17
provision a set of resources to. The input of the least-cost-scheduler is a
18
set of objective-functions, called the 'cost-functions', a weight for each
19
cost-function, and a list of candidate hosts (gathered via FilterHosts).
21
The cost-function and weights are tabulated, and the host with the least cost
22
is then selected for provisioning.
28
from nova import flags
29
from nova import log as logging
30
from nova.scheduler import base_scheduler
31
from nova import utils
32
from nova import exception
34
LOG = logging.getLogger('nova.scheduler.least_cost')
37
flags.DEFINE_list('least_cost_scheduler_cost_functions',
38
['nova.scheduler.least_cost.noop_cost_fn'],
39
'Which cost functions the LeastCostScheduler should use.')
42
# TODO(sirp): Once we have enough of these rules, we can break them out into a
43
# cost_functions.py file (perhaps in a least_cost_scheduler directory)
44
flags.DEFINE_integer('noop_cost_fn_weight', 1,
45
'How much weight to give the noop cost function')
46
flags.DEFINE_integer('compute_fill_first_cost_fn_weight', 1,
47
'How much weight to give the fill-first cost function')
50
def noop_cost_fn(host):
51
"""Return a pre-weight cost of 1 for each host"""
55
def compute_fill_first_cost_fn(host):
56
"""Prefer hosts that have less ram available, filter_hosts will exclude
57
hosts that don't have enough ram.
59
hostname, service = host
60
caps = service.get("compute", {})
61
free_mem = caps.get("host_memory_free", 0)
65
def normalize_list(L):
66
"""Normalize an array of numbers such that each element satisfies:
73
return [(float(e) / max_) for e in L]
77
def weighted_sum(domain, weighted_fns, normalize=True):
78
"""Use the weighted-sum method to compute a score for an array of objects.
79
Normalize the results of the objective-functions so that the weights are
80
meaningful regardless of objective-function's range.
82
domain - input to be scored
83
weighted_fns - list of weights and functions like:
84
[(weight, objective-functions)]
86
Returns an unsorted list of scores. To pair with hosts do:
90
# { domain1: [score1, score2, ..., scoreM]
92
# domainN: [score1, score2, ..., scoreM] }
93
score_table = collections.defaultdict(list)
94
for weight, fn in weighted_fns:
95
scores = [fn(elem) for elem in domain]
97
norm_scores = normalize_list(scores)
100
for idx, score in enumerate(norm_scores):
101
weighted_score = score * weight
102
score_table[idx].append(weighted_score)
104
# Sum rows in table to compute score for each element in domain
106
for idx in sorted(score_table):
107
elem_score = sum(score_table[idx])
108
domain_scores.append(elem_score)
112
class LeastCostScheduler(base_scheduler.BaseScheduler):
113
def __init__(self, *args, **kwargs):
114
self.cost_fns_cache = {}
115
super(LeastCostScheduler, self).__init__(*args, **kwargs)
117
def get_cost_fns(self, topic):
118
"""Returns a list of tuples containing weights and cost functions to
119
use for weighing hosts
121
if topic in self.cost_fns_cache:
122
return self.cost_fns_cache[topic]
124
for cost_fn_str in FLAGS.least_cost_scheduler_cost_functions:
125
if '.' in cost_fn_str:
126
short_name = cost_fn_str.split('.')[-1]
128
short_name = cost_fn_str
129
cost_fn_str = "%s.%s.%s" % (
130
__name__, self.__class__.__name__, short_name)
131
if not (short_name.startswith('%s_' % topic) or
132
short_name.startswith('noop')):
136
# NOTE(sirp): import_class is somewhat misnamed since it can
137
# any callable from a module
138
cost_fn = utils.import_class(cost_fn_str)
139
except exception.ClassNotFound:
140
raise exception.SchedulerCostFunctionNotFound(
141
cost_fn_str=cost_fn_str)
144
flag_name = "%s_weight" % cost_fn.__name__
145
weight = getattr(FLAGS, flag_name)
146
except AttributeError:
147
raise exception.SchedulerWeightFlagNotFound(
149
cost_fns.append((weight, cost_fn))
151
self.cost_fns_cache[topic] = cost_fns
154
def weigh_hosts(self, topic, request_spec, hosts):
155
"""Returns a list of dictionaries of form:
156
[ {weight: weight, hostname: hostname, capabilities: capabs} ]
158
cost_fns = self.get_cost_fns(topic)
159
costs = weighted_sum(domain=hosts, weighted_fns=cost_fns)
163
for cost, (hostname, service) in zip(costs, hosts):
164
caps = service[topic]
165
weight_log.append("%s: %s" % (hostname, "%.2f" % cost))
166
weight_dict = dict(weight=cost, hostname=hostname,
168
weighted.append(weight_dict)
170
LOG.debug(_("Weighted Costs => %s") % weight_log)