Skip to content

Instantly share code, notes, and snippets.

@cyli
Last active August 29, 2015 14:12
Show Gist options
  • Save cyli/0303219c930ff86cbd74 to your computer and use it in GitHub Desktop.
Save cyli/0303219c930ff86cbd74 to your computer and use it in GitHub Desktop.
@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)
"""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