Consider existing nodes when allocating to a target

When allocations were made for small numbers of nodes (eg, one at
a time), they disproportionately ended up allocated to one target
because the algorithm was only considering the nodes it was
allocating at that moment rather than all nodes.  Change it to
consider existing nodes and calculate a desired number of nodes
for each target in consideration and try to meet that target.
This means that now if nodes are created one at a time, they will
be added to targets as needed to try to achieve the balance
specified in the config file.

Change-Id: Iede3bbf6fb406b61e5177ea046cbf07324335352
This commit is contained in:
James E. Blair 2013-10-14 15:42:07 -07:00
parent 7ad830592d
commit 691c37009b
2 changed files with 39 additions and 6 deletions

View File

@ -108,8 +108,8 @@ class AllocationRequest(object):
def __repr__(self): def __repr__(self):
return '<AllocationRequest for %s of %s>' % (self.amount, self.name) return '<AllocationRequest for %s of %s>' % (self.amount, self.name)
def addTarget(self, target, min_ready): def addTarget(self, target, min_ready, current):
art = AllocationRequestTarget(self, target, min_ready) art = AllocationRequestTarget(self, target, min_ready, current)
self.request_targets[target] = art self.request_targets[target] = art
def addProvider(self, provider, target): def addProvider(self, provider, target):
@ -203,22 +203,48 @@ class AllocationGrant(object):
def makeAllocations(self): def makeAllocations(self):
# Allocate this grant to the linked targets using min_ready as # Allocate this grant to the linked targets using min_ready as
# a weight. Calculate the total min_ready. # a weight for the total number of nodes of this image that
# should be assigned to the target. Calculate the total
# min_ready as well as the total number of nodes for this image.
total_min_ready = 0.0 total_min_ready = 0.0
total_current = 0
for agt in self.targets: for agt in self.targets:
total_min_ready += agt.request_target.min_ready total_min_ready += agt.request_target.min_ready
total_current += agt.request_target.current
amount = self.amount amount = self.amount
# Add the nodes in this allocation to the total number of
# nodes for this image so that we're setting our target
# allocations based on a portion of the total future nodes.
total_current += amount
for agt in self.targets: for agt in self.targets:
# Calculate the weight from the config file (as indicated
# by min_ready)
if total_min_ready: if total_min_ready:
ratio = float(agt.request_target.min_ready) / total_min_ready ratio = float(agt.request_target.min_ready) / total_min_ready
else: else:
ratio = 0.0 ratio = 0.0
allocation = int(round(amount * ratio)) # Take the min_ready weight and apply it to the total
# number of nodes to this image to figure out how many of
# the total nodes should ideally be on this target.
desired_count = int(round(ratio * total_current))
# The number of nodes off from our calculated target.
delta = desired_count - agt.request_target.current
# Use the delta as the allocation for this target, but
# make sure it's bounded by 0 and the number of nodes we
# have available to allocate.
allocation = min(delta, amount)
allocation = max(allocation, 0)
# The next time through the loop, we have reduced our # The next time through the loop, we have reduced our
# grant by this amount. # grant by this amount.
amount -= allocation amount -= allocation
# Similarly we have reduced the total weight. # Similarly we have reduced the total weight.
total_min_ready -= agt.request_target.min_ready total_min_ready -= agt.request_target.min_ready
# Don't consider this target's count in the total number
# of nodes in the next iteration, nor the nodes we have
# just allocated.
total_current -= agt.request_target.current
total_current -= allocation
# Set the amount of this allocation. # Set the amount of this allocation.
agt.allocate(allocation) agt.allocate(allocation)
@ -234,10 +260,11 @@ class AllocationTarget(object):
class AllocationRequestTarget(object): class AllocationRequestTarget(object):
"""A request associated with a target to which nodes may be assigned.""" """A request associated with a target to which nodes may be assigned."""
def __init__(self, request, target, min_ready): def __init__(self, request, target, min_ready, current):
self.target = target self.target = target
self.request = request self.request = request
self.min_ready = min_ready self.min_ready = min_ready
self.current = current
class AllocationGrantTarget(object): class AllocationGrantTarget(object):
@ -258,3 +285,6 @@ class AllocationGrantTarget(object):
# specific provider that should be assigned to a specific # specific provider that should be assigned to a specific
# target. # target.
self.amount = amount self.amount = amount
# Update the number of nodes of this image that are assigned
# to this target to assist in other allocation calculations
self.request_target.current += amount

View File

@ -886,8 +886,11 @@ class NodePool(threading.Thread):
# loop. # loop.
ar = allocation.AllocationRequest(image.name, ar = allocation.AllocationRequest(image.name,
image_demand[image.name]) image_demand[image.name])
nodes = session.getNodes(image_name=image_name,
target_name=target.name)
allocation_requests[image.name] = ar allocation_requests[image.name] = ar
ar.addTarget(at, image.min_ready) ar.addTarget(at, image.min_ready, len(nodes))
for provider in image.providers.values(): for provider in image.providers.values():
# This request may be supplied by this provider # This request may be supplied by this provider
# (and nodes from this provider supplying this # (and nodes from this provider supplying this