summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorRick Harris <rick.harris@rackspace.com>2011-05-17 10:45:19 -0500
committerRick Harris <rick.harris@rackspace.com>2011-05-17 10:45:19 -0500
commit23bbbfcd3317859d44dba7da7996a978ad922543 (patch)
tree0cada1d996b4aacb341274c98c5b26d65ef1c682
parent68e34c790612f3250bd902cc87a0ab9d3d69abfb (diff)
downloadnova-23bbbfcd3317859d44dba7da7996a978ad922543.tar.gz
nova-23bbbfcd3317859d44dba7da7996a978ad922543.tar.xz
nova-23bbbfcd3317859d44dba7da7996a978ad922543.zip
First cut at least cost scheduler
-rw-r--r--nova/scheduler/least_cost.py79
-rw-r--r--nova/tests/test_least_cost_scheduler.py39
2 files changed, 118 insertions, 0 deletions
diff --git a/nova/scheduler/least_cost.py b/nova/scheduler/least_cost.py
new file mode 100644
index 000000000..75dde81ca
--- /dev/null
+++ b/nova/scheduler/least_cost.py
@@ -0,0 +1,79 @@
+import collections
+
+# TODO(sirp): this should be just `zone_aware` to match naming scheme
+# TODO(sirp): perhaps all zone-aware stuff should go under a `zone_aware`
+# module
+from nova.scheduler import zone_aware_scheduler
+
+class LeastCostScheduler(zone_aware_scheduler.ZoneAwareScheduler):
+ def get_cost_fns(self):
+ """Returns a list of tuples containing weights and cost functions to
+ use for weighing hosts
+ """
+ cost_fns = []
+
+ return cost_fns
+
+ def weigh_hosts(self, num, specs, hosts):
+ """
+ Returns a list of dictionaries of form:
+ [ {weight: weight, hostname: hostname} ]
+ """
+ # FIXME(sirp): weigh_hosts should handle more than just instances
+ cost_fns = []
+ hosts = []
+ cost_hosts = weighted_sum(domain=hosts, weighted_fns=self.get_cost_fns())
+
+ # TODO convert hosts back to hostnames
+ weight_hostnames = []
+ return weight_hostnames
+
+def normalize_list(L):
+ """Normalize an array of numbers such that each element satisfies:
+ 0 <= e <= 1
+ """
+ if not L:
+ return L
+ max_ = max(L)
+ if max_ > 0:
+ return [(float(e) / max_) for e in L]
+ return L
+
+def weighted_sum(domain, weighted_fns, normalize=True):
+ """
+ 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.
+
+ domain - input to be scored
+ weighted_fns - list of weights and functions like:
+ [(weight, objective-functions)]
+
+ Returns an unsorted list like: [(score, elem)]
+ """
+ # Table of form:
+ # { domain1: [score1, score2, ..., scoreM]
+ # ...
+ # domainN: [score1, score2, ..., scoreM] }
+ score_table = collections.defaultdict(list)
+
+ for weight, fn in weighted_fns:
+ scores = [fn(elem) for elem in domain]
+
+ if normalize:
+ norm_scores = normalize_list(scores)
+ else:
+ norm_scores = scores
+
+ for idx, score in enumerate(norm_scores):
+ weighted_score = score * weight
+ score_table[idx].append(weighted_score)
+
+ # Sum rows in table to compute score for each element in domain
+ domain_scores = []
+ for idx in sorted(score_table):
+ elem_score = sum(score_table[idx])
+ elem = domain[idx]
+ domain_scores.append(elem_score)
+
+ return domain_scores
diff --git a/nova/tests/test_least_cost_scheduler.py b/nova/tests/test_least_cost_scheduler.py
new file mode 100644
index 000000000..a3a18a09f
--- /dev/null
+++ b/nova/tests/test_least_cost_scheduler.py
@@ -0,0 +1,39 @@
+from nova import test
+from nova.scheduler import least_cost
+
+MB = 1024 * 1024
+
+class FakeHost(object):
+ def __init__(self, host_id, free_ram, io):
+ self.id = host_id
+ self.free_ram = free_ram
+ self.io = io
+
+class WeightedSumTest(test.TestCase):
+ def test_empty_domain(self):
+ domain = []
+ weighted_fns = []
+ result = least_cost.weighted_sum(domain, weighted_fns)
+ expected = []
+ self.assertEqual(expected, result)
+
+ def test_basic_costing(self):
+ hosts = [
+ FakeHost(1, 512 * MB, 100),
+ FakeHost(2, 256 * MB, 400),
+ FakeHost(3, 512 * MB, 100)
+ ]
+
+ weighted_fns = [
+ (1, lambda h: h.free_ram), # Fill-first, free_ram is a *cost*
+ (2, lambda h: h.io), # Avoid high I/O
+ ]
+
+ costs = least_cost.weighted_sum(domain=hosts, weighted_fns=weighted_fns)
+
+ # Each 256 MB unit of free-ram contributes 0.5 points by way of:
+ # cost = weight * (score/max_score) = 1 * (256/512) = 0.5
+ # Each 100 iops of IO adds 0.5 points by way of:
+ # cost = 2 * (100/400) = 2 * 0.25 = 0.5
+ expected = [1.5, 2.5, 1.5]
+ self.assertEqual(expected, costs)