Last active
August 29, 2015 14:12
-
-
Save cyli/0303219c930ff86cbd74 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
@implementer(ILBDescription) | |
@attributes([Attribute("lb_id", instance_of=str)]) | |
class RCv3Description(object): | |
""" | |
Information representing a Rackspace RCv3 node mapping. | |
:ivar str lb_id: The Load Balancer ID. | |
""" | |
def equivalent_definition(self, other_description): | |
""" | |
Whether the other description is also a :class:`RCv3Description` and | |
whether it has the same load balancer ID. | |
See :func:`ILBDescription.equivalent_definition`. | |
""" | |
return (isinstance(other_description, RCv3Description) and | |
other_description.lb_id == self.lb_id) | |
@implementer(ILBNode) | |
@attributes([Attribute("node_id", instance_of=str), | |
Attribute("description", instance_of=CLBDescription), | |
Attribute("cloud_server_id", instance_of=str), | |
class RCv3Node(object): | |
""" | |
A Rackspace RackConnect V3 Load Balancer Pool node. Such a node cannot be drained. | |
:ivar str node_id: The ID of the node, which is represents a cloud server mapped to the lb. | |
:obj:`ILBNode.node_id`. | |
:ivar description: The description of how the node should be set up. See | |
:obj:`ILBNode.description`. | |
:type: :class:`ILBDescription` provider | |
:ivar str cloud_server_id: The ID cloud server the node is mapped to. | |
""" | |
def matches(self, server): | |
""" | |
See :func:`ILBNode.matches`. | |
""" | |
return (isinstance(server, NovaServer) and | |
server.id == self.cloud_server_id) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
"""Code related to creating a plan for convergence.""" | |
from pyrsistent import pbag, pset | |
from toolz.curried import filter, groupby | |
from toolz.itertoolz import concat, concatv, mapcat | |
from otter.convergence.model import ( | |
ServerState, IDrainable, CLBDescription, CLBNode, CLBNodeCondition) | |
from otter.convergence.steps import ( | |
AddNodesToLoadBalancer, | |
ChangeLoadBalancerNode, | |
CreateServer, | |
DeleteServer, | |
RemoveFromLoadBalancer, | |
SetMetadataItemOnServer, | |
) | |
from otter.util.fp import partition_bool, partition_groups | |
def _remove_from_lb_with_draining(timeout, nodes, now): | |
""" | |
Produce a series of steps that will eventually remove all the given nodes. | |
It does this in three steps: | |
For any particular node in ``nodes``: | |
1. If the timeout is greater than zero, and the node is ``ENABLED``, the | |
node will be changed to ``DRAINING``. | |
2. If the node is ``DRAINING``, and the timeout (greater than zero) has | |
already expired or there are no more active connections, the node will | |
be removed from the load balancer. If the timeout (greater than zero) | |
has not expired and active connections != 0, then nothing is done to the | |
node. | |
3. If the node is in any other state other than `DRAINING` or `ENABLED`, or | |
if the timeout is zero, it will be removed from the load balancer. | |
:param float timeout: the time the node should remain in draining until | |
removed | |
:param list nodes: `list` of :obj:`LBNode` that should be | |
drained, then removed | |
:param float now: number of seconds since the POSIX epoch indicating the | |
time at which the convergence was requested. | |
:rtype: `list` of :class:`IStep` | |
""" | |
to_drain = () | |
in_drain = () | |
# only put nodes into draining if a timeout is specified | |
if timeout > 0: | |
draining, to_drain = partition_bool( | |
lambda node: node.currently_draining(), | |
[node for node in nodes if IDrainable.providedBy(node)]) | |
in_drain = [node for node in draining | |
if not node.is_done_draining(now, timeout)] | |
removes = [RemoveNodeFromLoadBalancing(node=node) | |
for node in (set(nodes) - set(to_drain) - set(in_drain))] | |
changes = [DrainLoadBalancingNode(node=node) for node in to_drain] | |
return removes + changes | |
def _converge_lb_state(desired_lb_state, current_lb_nodes, server): | |
""" | |
Produce a series of steps to converge a server's current load balancer | |
state towards its desired load balancer state. | |
The server will be removed from any extra load balancers the server | |
is currently on, and it will be added on the correct port, with the correct | |
weight, and correct status, to the desired load balancers. | |
:param dict desired_lb_state: As per :obj:`DesiredGroupState`.desired_lbs | |
:param list current_lb_nodes: `list` of :obj:`LBNode` | |
:param str ip_address: the IP address of the server to converge | |
Note: this supports user customizable types (e.g. PRIMARY or SECONDARY), but | |
in practice it should probably only be added as PRIMARY. SECONDARY can only | |
be used if load balancer health monitoring is enabled, and would be used as | |
backup servers anyway. | |
:rtype: `list` of :class:`IStep` | |
""" | |
desired_existing = [(desired, node) for desired in desired_lb_state | |
for node in current_lb_nodes | |
if desired.equiv_definition(node.description)] | |
if desired_existing: | |
done_desired, good_nodes = zip(*desired_existing) | |
else: | |
done_desired = good_nodes = () | |
adds = [ | |
AddServerToLoadBalancing(server=server, description=desired) | |
for desired in set(desired_lb_state) - set(done_desired) | |
] | |
# Removes could be replaced with _remove_from_lb_with_draining if | |
# we wanted to support draining for moving load balancers too | |
removes = [ | |
RemoveNodeFromLoadBalancing(node=node) | |
for node in set(current_lb_nodes) - set(good_nodes) | |
] | |
changes = [ | |
ChangeLoadBalancingNode(node=node, desired=desired) | |
for node, desired in desired_existing if node.description != desired | |
] | |
return [step for step in (adds + removes + changes) if step is not None] | |
def _drain_and_delete(server, timeout, current_lb_nodes, now): | |
""" | |
If server is not already in draining state, put it into draining state. | |
If the server is free of load balancers, just delete it. | |
""" | |
lb_draining_steps = _remove_from_lb_with_draining(timeout, current_lb_nodes, | |
now) | |
# if there are no load balancers that are waiting on draining timeouts or | |
# connections, just delete the server too | |
if (len(lb_draining_steps) == len(current_lb_nodes) and | |
all([isinstance(step, RemoveFromLoadBalancer) | |
for step in lb_draining_steps])): | |
return lb_draining_steps + [DeleteServer(server_id=server.id)] | |
# if the server is not already in draining state, put it into draining | |
if server.state != ServerState.DRAINING: | |
return lb_draining_steps + [ | |
SetMetadataItemOnServer(server_id=server.id, | |
key='rax:auto_scaling_draining', | |
value='draining')] | |
return lb_draining_steps | |
# ... | |
def AddServerToLoadBalancing(server, description): | |
""" | |
Add a server to a load balancing entity as described by `description`. | |
:ivar server: The server to be added | |
:type server: :class:`NovaServer` | |
:ivar description: The description of the load balancer and how to add | |
the server to it. | |
:type description: :class:`ILBDescription` provider | |
In the cases where no steps are produced (because it is an unsupported | |
description type, or if there is no ServiceNet address), a reporting | |
step could be produced instead. However, there is currently too much of a | |
yet-to-be-merged backlog touching convergence and steps, so this should be | |
done in the future. | |
""" | |
if isinstance(description, CLBDescription): | |
if server.servicenet_address: | |
return AddNodesToCLB(lb_id=description.lb_id, | |
address_configs=(server.servicenet_address)) | |
def RemoveNodeFromLoadBalancing(node): | |
""" | |
Remove a node from the load balancing entity. | |
:ivar node: The node to be removed. | |
:type node: :class:`ILBNode` provider | |
In the cases where no steps are produced (because it is an unsupported | |
description type, etc.), a reporting step could be produced instead. | |
However, there is currently too much of a yet-to-be-merged backlog touching | |
convergence and steps, so this should be done in the future. | |
""" | |
if isinstance(node, CLBNode): | |
return RemoveFromCLB(lb_id=node.description.lb_id, | |
node_id=node.node_id) | |
def ChangeLoadBalancingNode(node, description): | |
""" | |
Change the configuration of a load balancer node. | |
:ivar node: The node to be changed. | |
:type node: :class:`ILBNode` provider | |
:ivar description: The description of the load balancer and how to add | |
the server to it. | |
:type description: :class:`ILBDescription` provider | |
In the cases where no steps are produced (because it is an unsupported | |
description type), a reporting step could be produced instead. However, | |
there is currently too much of a yet-to-be-merged backlog touching | |
convergence and steps, so this should be done in the future. | |
""" | |
if type(node.description) == type(description): | |
if isinstance(description, CLBDescription): | |
return ChangeCLBNode(lb_id=description.lb_id, node_id=node.node_id, | |
condition=description.condition, | |
weight=description.weight, | |
type=description.type) | |
def DrainLoadBalancingNode(node): | |
""" | |
Drain the node balancing node. | |
:ivar node: The node to be changed. | |
:type node: :class:`ILBNode` provider | |
In the cases where no steps are produced (because it is an unsupported | |
description type), a reporting step could be produced instead. However, | |
there is currently too much of a yet-to-be-merged backlog touching | |
convergence and steps, so this should be done in the future. | |
""" | |
if isinstance(node, CLBNode): | |
return ChangeCLBNode(lb_id=node.description.lb_id, node_id=node.node_id, | |
condition=CLBNodeCondition.DRAINING, | |
weight=node.description.weight, | |
type=node.description.type) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment