Source code for ahoyhoy.lb.iloadbalancer
"""
Abstract Load Balancer
"""
import abc
import logging
from six import add_metaclass
from .providers.iprovider import IProvider
from ..endpoints import Endpoint
from .exceptions import NoAvailableEndpointsLbException
logger = logging.getLogger(__name__)
@add_metaclass(abc.ABCMeta)
[docs]class ILoadBalancer(object):
"""
Base class for load balancers.
"""
def __init__(self, provider, session=None):
"""
:param provider: any instance of :class:`~ahoyhoy.lb.providers.iprovider.IProvider`
:param session: custom session
"""
logger.debug("Create LB instance.")
self._check_provider(provider)
self._provider = provider
self._session = session
self._endpoints = {} # all endpoints
self._up = [] # create an empty `up` list
self._down = [] # create an empty `down` list
self.update() # update the `up` list with the new hosts
def _check_provider(self, provider):
assert isinstance(provider, IProvider)
@abc.abstractmethod
def _choose_next(self):
"""
Choose the next item from the list by given algorithm
"""
pass # pragma: no cover
[docs] def pick(self):
"""
Will only return nodes which have :class:`~ahoyhoy.circuit.OpenState`.
"""
logger.debug("Pick available endpoint.")
if not self._up:
logger.exception("self._up list with endpoints is empty: NoAvailableEndpointsLbException")
raise NoAvailableEndpointsLbException('No endpoints in closed state. Nothing to choose.')
while self._up:
ep = self._choose_next()
if ep.available:
return ep
else:
self._up.remove(ep.host)
self._down.insert(0, ep.host)
logger.exception("Tried all the endpoints, nothing is available: NoAvailableEndpointsLbException")
raise NoAvailableEndpointsLbException('No endpoints in closed state. Nothing to choose.')
[docs] def update(self):
"""
Update the hosts list with new hosts (if there are some).
"""
new_hosts = list(self._provider.get_list())
new_up_hosts = [h for h in new_hosts if h not in self._down]
self._up = self._remove_duplicates_with_preserving_order(self._up + new_up_hosts)
logger.debug("Endpoints list was updated: %s", self._up)
@staticmethod
def _remove_duplicates_with_preserving_order(seq):
# http://stackoverflow.com/a/480227/4492395
seen = set()
# Hoisting the lookup out of the loop, so it doesn't
# have to lookup for the 'add' attribute each time.
# Instead I have my seen.add method ready right away!!
seen_add = seen.add
return [x for x in seq if not (x in seen or seen_add(x))]
[docs] def get_or_create_endpoint(self, host):
"""
Concrete method to create an Endpoint from a `Host` object
:param host: `Host('host', 'port')` namedtuple
"""
logger.debug("Execute get_or_create_endpoint method.")
if host in self._endpoints:
logger.debug("Endpoint with host %s was found in self._endpoints %s", host, self._endpoints)
return self._endpoints[host]
ep = Endpoint(host, session=self._session)
self._endpoints[host] = ep
logger.debug(
"Endpoint with host %s wasn't found in self._endpoints. Create the new onr and update the list: %s",
host, self._endpoints)
return ep