lighthouse/haproxy/config.py
import collections
import datetime
import logging
import six
from .stanzas.section import Section
from .stanzas.meta import MetaFrontendStanza
from .stanzas.frontend import FrontendStanza
from .stanzas.backend import BackendStanza
from .stanzas.peers import PeersStanza
logger = logging.getLogger(__name__)
class HAProxyConfig(object):
"""
Class for generating HAProxy config file content.
Requires global and defaults stanzas to be passed, can optionally take
a `stats_stanza` for enabling a stats portal.
"""
def __init__(
self,
global_stanza, defaults_stanza,
proxy_stanzas=None, stats_stanza=None, meta_clusters=None,
bind_address=None
):
self.global_stanza = global_stanza
self.defaults_stanza = defaults_stanza
self.proxy_stanzas = proxy_stanzas or []
self.stats_stanza = stats_stanza
self.meta_clusters = meta_clusters or {}
self.bind_address = bind_address
def generate(self, clusters, version=None):
"""
Generates HAProxy config file content based on a given list of
clusters.
"""
now = datetime.datetime.now()
sections = [
Section(
"Auto-generated by Lighthouse (%s)" % now.strftime("%c"),
self.global_stanza,
self.defaults_stanza
)
]
meta_stanzas = [
MetaFrontendStanza(
name, self.meta_clusters[name]["port"],
self.meta_clusters[name].get("frontend", []), members,
self.bind_address
)
for name, members
in six.iteritems(self.get_meta_clusters(clusters))
]
frontend_stanzas = [
FrontendStanza(cluster, self.bind_address)
for cluster in clusters
if "port" in cluster.haproxy
]
backend_stanzas = [BackendStanza(cluster) for cluster in clusters]
if version and version >= (1, 5, 0):
peers_stanzas = [PeersStanza(cluster) for cluster in clusters]
else:
peers_stanzas = []
sections.extend([
Section("Frontend stanzas for ACL meta clusters", *meta_stanzas),
Section("Per-cluster frontend definitions", *frontend_stanzas),
Section("Per-cluster backend definitions", *backend_stanzas),
Section("Per-cluster peer listings", *peers_stanzas),
Section("Individual proxy definitions", *self.proxy_stanzas),
])
if self.stats_stanza:
sections.append(
Section("Listener for stats web interface", self.stats_stanza)
)
return "\n\n\n".join([str(section) for section in sections]) + "\n"
def get_meta_clusters(self, clusters):
"""
Returns a dictionary keyed off of meta cluster names, where the values
are lists of clusters associated with the meta cluster name.
If a meta cluster name doesn't have a port defined in the
`meta_cluster_ports` attribute an error is given and the meta cluster
is removed from the mapping.
"""
meta_clusters = collections.defaultdict(list)
for cluster in clusters:
if not cluster.meta_cluster:
continue
meta_clusters[cluster.meta_cluster].append(cluster)
unconfigured_meta_clusters = [
name for name in meta_clusters.keys()
if name not in self.meta_clusters
]
for name in unconfigured_meta_clusters:
logger.error("Meta cluster %s not configured!")
del meta_clusters[name]
return meta_clusters