228 lines
8.1 KiB
Python
228 lines
8.1 KiB
Python
# Copyright (c) 2018 OpenStack Foundation
|
|
#
|
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
|
# not use this file except in compliance with the License. You may obtain
|
|
# a copy of the License at
|
|
#
|
|
# http://www.apache.org/licenses/LICENSE-2.0
|
|
#
|
|
# Unless required by applicable law or agreed to in writing, software
|
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
# License for the specific language governing permissions and limitations
|
|
# under the License.
|
|
|
|
import os
|
|
|
|
import jinja2
|
|
from octavia_lib.common import constants as lib_consts
|
|
|
|
from octavia.common.config import cfg
|
|
from octavia.common import constants
|
|
from octavia.common import utils as octavia_utils
|
|
|
|
|
|
CONF = cfg.CONF
|
|
|
|
PROTOCOL_MAP = {
|
|
constants.PROTOCOL_UDP: 'udp',
|
|
lib_consts.PROTOCOL_SCTP: 'sctp'
|
|
}
|
|
|
|
BALANCE_MAP = {
|
|
constants.LB_ALGORITHM_ROUND_ROBIN: 'wrr',
|
|
constants.LB_ALGORITHM_LEAST_CONNECTIONS: 'lc',
|
|
constants.LB_ALGORITHM_SOURCE_IP: 'sh'
|
|
}
|
|
|
|
BASE_PATH = CONF.haproxy_amphora.base_path
|
|
|
|
CHECK_SCRIPT_NAME = 'udp_check.sh'
|
|
|
|
KEEPALIVED_LVS_TEMPLATE = os.path.abspath(
|
|
os.path.join(os.path.dirname(__file__),
|
|
'templates/keepalivedlvs.cfg.j2'))
|
|
|
|
JINJA_ENV = None
|
|
|
|
|
|
class LvsJinjaTemplater(object):
|
|
|
|
def __init__(self, base_amp_path=None, keepalivedlvs_template=None):
|
|
"""Keepalived LVS configuration generation
|
|
|
|
:param base_amp_path: Base path for amphora data
|
|
:param keepalivedlvs_template: Absolute path to Jinja template
|
|
"""
|
|
|
|
self.base_amp_path = base_amp_path or BASE_PATH
|
|
self.keepalivedlvs_template = (keepalivedlvs_template or
|
|
KEEPALIVED_LVS_TEMPLATE)
|
|
|
|
def build_config(self, listener, **kwargs):
|
|
"""Convert a logical configuration to the Keepalived LVS version
|
|
|
|
:param listener: The listener configuration
|
|
:return: Rendered configuration
|
|
"""
|
|
return self.render_loadbalancer_obj(listener)
|
|
|
|
def _get_template(self):
|
|
"""Returns the specified Jinja configuration template."""
|
|
global JINJA_ENV
|
|
if not JINJA_ENV:
|
|
template_loader = jinja2.FileSystemLoader(
|
|
searchpath=os.path.dirname(self.keepalivedlvs_template))
|
|
JINJA_ENV = jinja2.Environment(
|
|
autoescape=True,
|
|
loader=template_loader,
|
|
trim_blocks=True,
|
|
lstrip_blocks=True,
|
|
extensions=['jinja2.ext.do'])
|
|
return JINJA_ENV.get_template(os.path.basename(
|
|
self.keepalivedlvs_template))
|
|
|
|
def render_loadbalancer_obj(self, listener, **kwargs):
|
|
"""Renders a templated configuration from a load balancer object
|
|
|
|
:param host_amphora: The Amphora this configuration is hosted on
|
|
:param listener: The listener configuration
|
|
:return: Rendered configuration
|
|
"""
|
|
loadbalancer = self._transform_loadbalancer(
|
|
listener.load_balancer,
|
|
listener)
|
|
return self._get_template().render(
|
|
{'loadbalancer': loadbalancer},
|
|
constants=constants,
|
|
lib_consts=lib_consts)
|
|
|
|
def _transform_loadbalancer(self, loadbalancer, listener):
|
|
"""Transforms a load balancer into an object that will
|
|
|
|
be processed by the templating system
|
|
"""
|
|
t_listener = self._transform_listener(listener)
|
|
ret_value = {
|
|
'id': loadbalancer.id,
|
|
'vip_address': loadbalancer.vip.ip_address,
|
|
'listener': t_listener,
|
|
'enabled': loadbalancer.enabled
|
|
}
|
|
return ret_value
|
|
|
|
def _transform_listener(self, listener):
|
|
"""Transforms a listener into an object that will
|
|
|
|
be processed by the templating system
|
|
"""
|
|
ret_value = {
|
|
'id': listener.id,
|
|
'protocol_port': listener.protocol_port,
|
|
'protocol_mode': PROTOCOL_MAP[listener.protocol],
|
|
'enabled': listener.enabled
|
|
}
|
|
if listener.connection_limit and listener.connection_limit > -1:
|
|
ret_value['connection_limit'] = listener.connection_limit
|
|
if (listener.default_pool and
|
|
listener.default_pool.provisioning_status !=
|
|
constants.PENDING_DELETE):
|
|
ret_value['default_pool'] = self._transform_pool(
|
|
listener.default_pool)
|
|
return ret_value
|
|
|
|
def _transform_pool(self, pool):
|
|
"""Transforms a pool into an object that will
|
|
|
|
be processed by the templating system
|
|
"""
|
|
ret_value = {
|
|
'id': pool.id,
|
|
'protocol': PROTOCOL_MAP[pool.protocol],
|
|
'lb_algorithm': (BALANCE_MAP.get(pool.lb_algorithm) or
|
|
BALANCE_MAP[constants.LB_ALGORITHM_ROUND_ROBIN]),
|
|
'members': [],
|
|
'health_monitor': '',
|
|
'session_persistence': '',
|
|
'enabled': pool.enabled
|
|
}
|
|
members_gen = (mem for mem in pool.members if
|
|
mem.provisioning_status != constants.PENDING_DELETE)
|
|
members = [self._transform_member(x) for x in members_gen]
|
|
ret_value['members'] = members
|
|
if (pool.health_monitor and
|
|
pool.health_monitor.provisioning_status !=
|
|
constants.PENDING_DELETE):
|
|
ret_value['health_monitor'] = self._transform_health_monitor(
|
|
pool.health_monitor)
|
|
if pool.session_persistence:
|
|
func = self._transform_session_persistence
|
|
ret_value['session_persistence'] = func(
|
|
pool.session_persistence)
|
|
return ret_value
|
|
|
|
@staticmethod
|
|
def _transform_session_persistence(persistence):
|
|
"""Transforms session persistence into an object that will
|
|
|
|
be processed by the templating system
|
|
"""
|
|
return {
|
|
'type': persistence.type,
|
|
'persistence_timeout': persistence.persistence_timeout,
|
|
'persistence_granularity': persistence.persistence_granularity
|
|
}
|
|
|
|
@staticmethod
|
|
def _transform_member(member):
|
|
"""Transforms a member into an object that will
|
|
|
|
be processed by the templating system
|
|
"""
|
|
return {
|
|
'id': member.id,
|
|
'address': member.ip_address,
|
|
'protocol_port': member.protocol_port,
|
|
'weight': member.weight,
|
|
'enabled': member.enabled,
|
|
'monitor_address': member.monitor_address,
|
|
'monitor_port': member.monitor_port
|
|
}
|
|
|
|
def _get_default_lvs_check_script_path(self, monitor_type):
|
|
if monitor_type == constants.HEALTH_MONITOR_UDP_CONNECT:
|
|
return (CONF.haproxy_amphora.base_path +
|
|
'/lvs/check/' + CHECK_SCRIPT_NAME)
|
|
if monitor_type == lib_consts.HEALTH_MONITOR_SCTP:
|
|
return "amphora-health-checker sctp"
|
|
return None
|
|
|
|
def _transform_health_monitor(self, monitor):
|
|
"""Transforms a health monitor into an object that will
|
|
|
|
be processed by the templating system
|
|
"""
|
|
return_val = {
|
|
'id': monitor.id,
|
|
'type': monitor.type,
|
|
'delay': monitor.delay,
|
|
'timeout': monitor.timeout,
|
|
'enabled': monitor.enabled,
|
|
'fall_threshold': monitor.fall_threshold,
|
|
'check_script_path': (
|
|
self._get_default_lvs_check_script_path(monitor.type))
|
|
}
|
|
if monitor.type == constants.HEALTH_MONITOR_HTTP:
|
|
return_val.update({
|
|
'rise_threshold': monitor.rise_threshold,
|
|
'url_path': monitor.url_path,
|
|
'http_method': (monitor.http_method
|
|
if monitor.http_method ==
|
|
constants.HEALTH_MONITOR_HTTP_METHOD_GET else
|
|
None),
|
|
'expected_codes': (sorted(list(
|
|
octavia_utils.expand_expected_codes(
|
|
monitor.expected_codes)))
|
|
if monitor.expected_codes else [])})
|
|
return return_val
|