Add L7 jinja template updates

This commit modifies the jinja templates used by the rest and SSH
amphora drivers for generating the haproxy configurations that get
generated for each listener.  It is one in a chain of commits
designed to keep the size of each individual commit
manageable / reviewable. Documentation updates will come in a later
commit.

Per Octavia team discussion, this commit also moves these templates
to the octavia/common/templates directory.

Change-Id: I18aa6080dd19b4afbbdff1ccc81cff7d96b993e6
Partially-Implements: blueprint lbaas-l7-rules
Partially-Implements: blueprint layer-7-switching
This commit is contained in:
Stephen Balukoff 2016-02-09 22:16:19 -08:00
parent fbc4a0aa4d
commit 2fabcabc4e
13 changed files with 652 additions and 191 deletions

View File

@ -1,35 +0,0 @@
{# # Copyright (c) 2015 Rackspace
#
# 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.
#
#}
{% extends 'haproxy_proxies.template' %}
{% set loadbalancer_name = loadbalancer.name %}
{% set usergroup = user_group %}
{% set sock_path = stats_sock %}
{% block peers %}
{% from 'haproxy_proxies.template' import peers_macro%}
{{ peers_macro(constants, loadbalancer.listener) }}
{% endblock peers %}
{% block proxies %}
{% from 'haproxy_proxies.template' import frontend_macro as frontend_macro, backend_macro%}
{{ frontend_macro(constants, loadbalancer.listener, loadbalancer.vip_address) }}
{% if loadbalancer.listener.default_pool %}
{{ backend_macro(constants, loadbalancer.listener, loadbalancer.listener.default_pool) }}
{% endif %}
{# TODO(sbalukoff): Will need to add pools referenced by L7Policies attached
# to the listener, but ensure each only gets listed once, eh.
#}
{% endblock proxies %}

View File

@ -1,114 +0,0 @@
{# # Copyright (c) 2015 Rackspace
#
# 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.
#
#}
{% extends 'haproxy_base.template' %}
{% macro peers_macro(constants,listener) %}
{% if listener.topology == constants.TOPOLOGY_ACTIVE_STANDBY %}
peers {{ "%s_peers"|format(listener.id.replace("-", ""))|trim() }}
{% for amp in listener.amphorae if amp.status == constants.AMPHORA_ALLOCATED %}
{# HAProxy has peer name limitations, thus the hash filter #}
peer {{ amp.id|hash_amp_id|replace('=', '') }} {{ amp.vrrp_ip }}:{{ listener.peer_port }}
{% endfor %}
{% endif %}
{% endmacro %}
{% macro bind_macro(constants, listener, lb_vip_address) %}
{% if listener.default_tls_path %}
{% set def_crt_opt = "ssl crt %s"|format(listener.default_tls_path)|trim() %}
{% else %}
{% set def_crt_opt = "" %}
{% endif %}
{% if listener.crt_dir %}
{% set crt_dir_opt = "crt %s"|format(listener.crt_dir)|trim() %}
{% else %}
{% set crt_dir_opt = "" %}
{% endif %}
bind {{ lb_vip_address }}:{{ listener.protocol_port }} {{ "%s %s"|format(def_crt_opt, crt_dir_opt)|trim() }}
{% endmacro %}
{% macro use_backend_macro(listener) %}
{% if listener.default_pool %}
default_backend {{ listener.default_pool.id }}
{% endif %}
{% endmacro %}
{% macro frontend_macro(constants, listener, lb_vip_address) %}
frontend {{ listener.id }}
option tcplog
{% if listener.connection_limit is defined %}
maxconn {{ listener.connection_limit }}
{% endif %}
{% if listener.protocol.lower() == constants.PROTOCOL_TERMINATED_HTTPS.lower() %}
redirect scheme https if !{ ssl_fc }
{% endif %}
{{ bind_macro(constants, listener, lb_vip_address)|trim() }}
mode {{ listener.protocol_mode }}
{% if listener.default_pool %}
default_backend {{ listener.default_pool.id }}
{% endif %}
{% endmacro %}
{% macro backend_macro(constants, listener, pool) %}
backend {{ pool.id }}
mode {{ pool.protocol }}
balance {{ pool.lb_algorithm }}
{% if pool.session_persistence %}
{% if pool.session_persistence.type == constants.SESSION_PERSISTENCE_SOURCE_IP %}
{% if listener.topology == constants.TOPOLOGY_ACTIVE_STANDBY %}
stick-table type ip size {{ pool.stick_size }} peers {{ "%s_peers"|format(listener.id.replace("-", ""))|trim() }}
{% else %}
stick-table type ip size {{ pool.stick_size }}
{% endif %}
stick on src
{% elif pool.session_persistence.type == constants.SESSION_PERSISTENCE_APP_COOKIE %}
{% if listener.topology == constants.TOPOLOGY_ACTIVE_STANDBY %}
stick-table type string len 64 size {{ pool.stick_size }} peers {{ "%s_peers"|format(listener.id.replace("-", ""))|trim() }}
{% else %}
stick-table type string len 64 size {{ pool.stick_size }}
{% endif %}
stick store-response res.cook({{ pool.session_persistence.cookie_name }})
stick match req.cook({{ pool.session_persistence.cookie_name }})
{% elif pool.session_persistence.type == constants.SESSION_PERSISTENCE_HTTP_COOKIE %}
cookie SRV insert indirect nocache
{% endif %}
{% endif %}
{% if pool.health_monitor %}
timeout check {{ pool.health_monitor.timeout }}
{% if pool.health_monitor.type == constants.HEALTH_MONITOR_HTTP or pool.health_monitor.type == constants.HEALTH_MONITOR_HTTPS %}
option httpchk {{ pool.health_monitor.http_method }} {{ pool.health_monitor.url_path }}
http-check expect rstatus {{ pool.health_monitor.expected_codes }}
{% endif %}
{% if pool.health_monitor.type == constants.HEALTH_MONITOR_HTTPS %}
option ssl-hello-chk
{% endif %}
{% endif %}
{% if pool.protocol.lower() == constants.PROTOCOL_HTTP.lower() %}
option forwardfor
{% endif %}
{% for member in pool.members %}
{% if pool.health_monitor %}
{% set hm_opt = " check inter %ds fall %d rise %d"|format(pool.health_monitor.delay, pool.health_monitor.fall_threshold, pool.health_monitor.rise_threshold) %}
{% else %}
{% set hm_opt = "" %}
{% endif %}
{%if pool.session_persistence.type == constants.SESSION_PERSISTENCE_HTTP_COOKIE %}
{% set persistence_opt = " cookie %s"|format(member.id) %}
{% else %}
{% set persistence_opt = "" %}
{% endif %}
{{ "server %s %s:%d weight %s%s%s"|e|format(member.id, member.address, member.protocol_port, member.weight, hm_opt, persistence_opt)|trim() }}
{% endfor %}
{% endmacro %}

View File

@ -26,10 +26,10 @@ from stevedore import driver as stevedore_driver
from octavia.amphorae.driver_exceptions import exceptions as driver_except
from octavia.amphorae.drivers import driver_base as driver_base
from octavia.amphorae.drivers.haproxy import exceptions as exc
from octavia.amphorae.drivers.haproxy.jinja import jinja_cfg
from octavia.amphorae.drivers.keepalived import vrrp_rest_driver
from octavia.common.config import cfg
from octavia.common import constants
from octavia.common.jinja.haproxy import jinja_cfg
from octavia.common.tls_utils import cert_parser
from octavia.i18n import _LW

View File

@ -13,6 +13,7 @@
# under the License.
import os
import re
import jinja2
import six
@ -42,7 +43,7 @@ BASE_CRT_DIR = BASE_PATH + '/certs'
HAPROXY_TEMPLATE = os.path.abspath(
os.path.join(os.path.dirname(__file__),
'templates/haproxy_listener.template'))
'templates/haproxy.cfg.j2'))
CONF = cfg.CONF
CONF.import_group('haproxy_amphora', 'octavia.common.config')
@ -180,7 +181,10 @@ class JinjaTemplater(object):
if listener.default_pool:
ret_value['default_pool'] = self._transform_pool(
listener.default_pool)
# TODO(sbalukoff): Handle pools referenced by L7Policies
pools = [self._transform_pool(x) for x in listener.pools]
ret_value['pools'] = pools
l7policies = [self._transform_l7policy(x) for x in listener.l7policies]
ret_value['l7policies'] = l7policies
return ret_value
def _transform_pool(self, pool):
@ -259,6 +263,52 @@ class JinjaTemplater(object):
'enabled': monitor.enabled,
}
def _transform_l7policy(self, l7policy):
"""Transforms an L7 policy into an object that will
be processed by the templating system
"""
ret_value = {
'id': l7policy.id,
'action': l7policy.action,
'redirect_url': l7policy.redirect_url,
'enabled': l7policy.enabled
}
if l7policy.redirect_pool:
ret_value['redirect_pool'] = self._transform_pool(
l7policy.redirect_pool)
else:
ret_value['redirect_pool'] = None
l7rules = [self._transform_l7rule(x) for x in l7policy.l7rules]
ret_value['l7rules'] = l7rules
return ret_value
def _transform_l7rule(self, l7rule):
"""Transforms an L7 rule into an object that will
be processed by the templating system
"""
return {
'id': l7rule.id,
'type': l7rule.type,
'compare_type': l7rule.compare_type,
'key': l7rule.key,
'value': self._escape_haproxy_config_string(l7rule.value),
'invert': l7rule.invert
}
@staticmethod
def _escape_haproxy_config_string(value):
"""Escapes certain characters in a given string such that
haproxy will parse the string as a single value
"""
# Escape backslashes first
value = re.sub(r'\\', r'\\\\', value)
# Spaces next
value = re.sub(' ', '\\ ', value)
return value
@staticmethod
def _expand_expected_codes(codes):
"""Expand the expected code string in set of codes.

View File

@ -1,4 +1,4 @@
{# # Copyright (c) 2015 Rackspace
{# Copyright (c) 2015 Rackspace
#
# 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

View File

@ -1,4 +1,4 @@
{# # Copyright (c) 2015 Rackspace
{# Copyright (c) 2015 Rackspace
#
# 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
@ -13,22 +13,27 @@
# under the License.
#
#}
{% extends 'haproxy_proxies.template' %}
{% extends 'base.j2' %}
{% from 'macros.j2' import frontend_macro, backend_macro %}
{% from 'macros.j2' import peers_macro %}
{% set loadbalancer_name = loadbalancer.name %}
{% set usergroup = user_group %}
{% set sock_path = stats_sock %}
{% block peers %}
{% from 'haproxy_proxies.template' import peers_macro%}
{{ peers_macro(constants, loadbalancer.listener) }}
{% endblock peers %}
{% block proxies %}
{% from 'haproxy_proxies.template' import frontend_macro as frontend_macro, backend_macro%}
{% for listener in loadbalancer.listeners %}
{{ frontend_macro(constants, listener, loadbalancer.vip_address) }}
{% if listener.default_pool %}
{{ backend_macro(constants, listener, listener.default_pool) }}
{% endif %}
{% endfor %}
{% endblock proxies %}
{{- frontend_macro(constants, loadbalancer.listener,
loadbalancer.vip_address) }}
{% for pool in loadbalancer.listener.pools %}
{{- backend_macro(constants, loadbalancer.listener, pool) }}
{% endfor %}
{% endblock proxies %}

View File

@ -0,0 +1,204 @@
{# Copyright (c) 2015 Rackspace
#
# 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.
#
#}
{% macro peers_macro(constants,listener) %}
{% if listener.topology == constants.TOPOLOGY_ACTIVE_STANDBY %}
peers {{ "%s_peers"|format(listener.id.replace("-", ""))|trim() }}
{% for amp in listener.amphorae if (
amp.status == constants.AMPHORA_ALLOCATED) %}
{# HAProxy has peer name limitations, thus the hash filter #}
peer {{ amp.id|hash_amp_id|replace('=', '') }} {{
amp.vrrp_ip }}:{{ listener.peer_port }}
{% endfor %}
{% endif %}
{% endmacro %}
{% macro bind_macro(constants, listener, lb_vip_address) %}
{% if listener.default_tls_path %}
{% set def_crt_opt = ("ssl crt %s"|format(
listener.default_tls_path)|trim()) %}
{% else %}
{% set def_crt_opt = "" %}
{% endif %}
{% if listener.crt_dir %}
{% set crt_dir_opt = "crt %s"|format(listener.crt_dir)|trim() %}
{% else %}
{% set crt_dir_opt = "" %}
{% endif %}
bind {{ lb_vip_address }}:{{ listener.protocol_port }} {{
"%s %s"|format(def_crt_opt, crt_dir_opt)|trim() }}
{% endmacro %}
{% macro l7rule_compare_type_macro(constants, ctype) %}
{% if ctype == constants.L7RULE_COMPARE_TYPE_REGEX %}
{{- "-m reg" -}}
{% elif ctype == constants.L7RULE_COMPARE_TYPE_STARTS_WITH %}
{{- "-m beg" -}}
{% elif ctype == constants.L7RULE_COMPARE_TYPE_ENDS_WITH %}
{{- "-m end" -}}
{% elif ctype == constants.L7RULE_COMPARE_TYPE_CONTAINS %}
{{- "-m sub" -}}
{% elif ctype == constants.L7RULE_COMPARE_TYPE_EQUAL_TO %}
{{- "-m str" -}}
{% endif %}
{% endmacro %}
{% macro l7rule_macro(constants, l7rule) %}
{% if l7rule.type == constants.L7RULE_TYPE_HOST_NAME %}
acl {{ l7rule.id }} req.hdr(host) -i {{ l7rule_compare_type_macro(
constants, l7rule.compare_type) }} {{ l7rule.value }}
{% elif l7rule.type == constants.L7RULE_TYPE_PATH %}
acl {{ l7rule.id }} path {{ l7rule_compare_type_macro(
constants, l7rule.compare_type) }} {{ l7rule.value }}
{% elif l7rule.type == constants.L7RULE_TYPE_FILE_TYPE %}
acl {{ l7rule.id }} path_end {{ l7rule_compare_type_macro(
constants, l7rule.compare_type) }} {{ l7rule.value }}
{% elif l7rule.type == constants.L7RULE_TYPE_HEADER %}
acl {{ l7rule.id }} req.hdr({{ l7rule.key }}) {{
l7rule_compare_type_macro(
constants, l7rule.compare_type) }} {{ l7rule.value }}
{% elif l7rule.type == constants.L7RULE_TYPE_COOKIE %}
acl {{ l7rule.id }} req.cook({{ l7rule.key }}) {{
l7rule_compare_type_macro(
constants, l7rule.compare_type) }} {{ l7rule.value }}
{% endif %}
{% endmacro %}
{% macro l7rule_invert_macro(invert) %}
{% if invert %}
{{- "!" -}}
{% endif %}
{% endmacro %}
{% macro l7rule_list_macro(l7policy) %}
{% for l7rule in l7policy.l7rules %}
{{- " " -}}{{- l7rule_invert_macro(l7rule.invert) -}}{{- l7rule.id -}}
{% endfor %}
{% endmacro %}
{% macro l7policy_macro(constants, l7policy) %}
{% for l7rule in l7policy.l7rules %}
{{- l7rule_macro(constants, l7rule) -}}
{% endfor %}
{% if l7policy.action == constants.L7POLICY_ACTION_REJECT %}
http-request deny if{{ l7rule_list_macro(l7policy) }}
{% elif l7policy.action == constants.L7POLICY_ACTION_REDIRECT_TO_URL %}
redirect location {{ l7policy.redirect_url }} if{{ l7rule_list_macro(
l7policy) }}
{% elif l7policy.action == constants.L7POLICY_ACTION_REDIRECT_TO_POOL %}
use_backend {{ l7policy.redirect_pool.id }} if{{ l7rule_list_macro(
l7policy) }}
{% endif %}
{% endmacro %}
{% macro frontend_macro(constants, listener, lb_vip_address) %}
frontend {{ listener.id }}
option tcplog
{% if listener.connection_limit is defined %}
maxconn {{ listener.connection_limit }}
{% endif %}
{% if (listener.protocol.lower() ==
constants.PROTOCOL_TERMINATED_HTTPS.lower()) %}
redirect scheme https if !{ ssl_fc }
{% endif %}
{{ bind_macro(constants, listener, lb_vip_address)|trim() }}
mode {{ listener.protocol_mode }}
{% for l7policy in listener.l7policies if (l7policy.enabled and
l7policy.l7rules|length > 0) %}
{{- l7policy_macro(constants, l7policy) -}}
{% endfor %}
{% if listener.default_pool %}
default_backend {{ listener.default_pool.id }}
{% endif %}
{% endmacro %}
{% macro member_macro(constants, pool, member) %}
{% if pool.health_monitor %}
{% set hm_opt = " check inter %ds fall %d rise %d"|format(
pool.health_monitor.delay, pool.health_monitor.fall_threshold,
pool.health_monitor.rise_threshold) %}
{% else %}
{% set hm_opt = "" %}
{% endif %}
{% if (pool.session_persistence.type ==
constants.SESSION_PERSISTENCE_HTTP_COOKIE) %}
{% set persistence_opt = " cookie %s"|format(member.id) %}
{% else %}
{% set persistence_opt = "" %}
{% endif %}
{{ "server %s %s:%d weight %s%s%s"|e|format(
member.id, member.address, member.protocol_port, member.weight,
hm_opt, persistence_opt)|trim() }}
{% endmacro %}
{% macro backend_macro(constants, listener, pool) %}
backend {{ pool.id }}
mode {{ pool.protocol }}
balance {{ pool.lb_algorithm }}
{% if pool.session_persistence %}
{% if (pool.session_persistence.type ==
constants.SESSION_PERSISTENCE_SOURCE_IP) %}
{% if listener.topology == constants.TOPOLOGY_ACTIVE_STANDBY %}
stick-table type ip size {{ pool.stick_size }} peers {{
"%s_peers"|format(listener.id.replace("-", ""))|trim() }}
{% else %}
stick-table type ip size {{ pool.stick_size }}
{% endif %}
stick on src
{% elif (pool.session_persistence.type ==
constants.SESSION_PERSISTENCE_APP_COOKIE) %}
{% if listener.topology == constants.TOPOLOGY_ACTIVE_STANDBY %}
stick-table type string len 64 size {{
pool.stick_size }} peers {{
"%s_peers"|format(listener.id.replace("-", ""))|trim() }}
{% else %}
stick-table type string len 64 size {{ pool.stick_size }}
{% endif %}
stick store-response res.cook({{ pool.session_persistence.cookie_name }})
stick match req.cook({{ pool.session_persistence.cookie_name }})
{% elif (pool.session_persistence.type ==
constants.SESSION_PERSISTENCE_HTTP_COOKIE) %}
cookie SRV insert indirect nocache
{% endif %}
{% endif %}
{% if pool.health_monitor %}
timeout check {{ pool.health_monitor.timeout }}
{% if (pool.health_monitor.type ==
constants.HEALTH_MONITOR_HTTP or pool.health_monitor.type ==
constants.HEALTH_MONITOR_HTTPS) %}
option httpchk {{ pool.health_monitor.http_method }} {{
pool.health_monitor.url_path }}
http-check expect rstatus {{ pool.health_monitor.expected_codes }}
{% endif %}
{% if pool.health_monitor.type == constants.HEALTH_MONITOR_HTTPS %}
option ssl-hello-chk
{% endif %}
{% endif %}
{% if pool.protocol.lower() == constants.PROTOCOL_HTTP.lower() %}
option forwardfor
{% endif %}
{% for member in pool.members %}
{{- member_macro(constants, pool, member) -}}
{% endfor %}
{% endmacro %}

View File

@ -16,8 +16,8 @@ import mock
import six.moves.builtins as builtins
from octavia.amphorae.backends.agent.api_server import listener
from octavia.amphorae.drivers.haproxy.jinja import jinja_cfg
from octavia.common import constants as consts
from octavia.common.jinja.haproxy import jinja_cfg
import octavia.tests.unit.base as base
from octavia.tests.unit.common.sample_configs import sample_configs

View File

@ -13,7 +13,7 @@
# License for the specific language governing permissions and limitations
# under the License.
from octavia.amphorae.drivers.haproxy.jinja import jinja_cfg
from octavia.common.jinja.haproxy import jinja_cfg
from octavia.tests.unit import base as base
from octavia.tests.unit.common.sample_configs import sample_configs
@ -27,7 +27,7 @@ class TestHaproxyCfg(base.TestCase):
def test_get_template(self):
template = self.jinja_cfg._get_template()
self.assertEqual('haproxy_listener.template', template.name)
self.assertEqual('haproxy.cfg.j2', template.name)
def test_render_template_tls(self):
fe = ("frontend sample_listener_id_1\n"
@ -259,6 +259,54 @@ class TestHaproxyCfg(base.TestCase):
sample_configs.sample_base_expected_config(backend=be),
rendered_obj)
def test_render_template_l7policies(self):
fe = ("frontend sample_listener_id_1\n"
" option tcplog\n"
" maxconn 98\n"
" bind 10.0.0.2:80\n"
" mode http\n"
" acl sample_l7rule_id_1 path -m beg /api\n"
" use_backend sample_pool_id_2 if sample_l7rule_id_1\n"
" acl sample_l7rule_id_2 req.hdr(Some-header) -m sub "
"This\\ string\\\\\\ with\\ stuff\n"
" acl sample_l7rule_id_3 req.cook(some-cookie) -m reg "
"this.*|that\n"
" redirect location http://www.example.com if "
"!sample_l7rule_id_2 sample_l7rule_id_3\n"
" acl sample_l7rule_id_4 path_end -m str jpg\n"
" acl sample_l7rule_id_5 req.hdr(host) -i -m end "
".example.com\n"
" http-request deny if sample_l7rule_id_4 "
"sample_l7rule_id_5\n"
" default_backend sample_pool_id_1\n\n")
be = ("backend sample_pool_id_1\n"
" mode http\n"
" balance roundrobin\n"
" cookie SRV insert indirect nocache\n"
" timeout check 31\n"
" option httpchk GET /index.html\n"
" http-check expect rstatus 418\n"
" option forwardfor\n"
" server sample_member_id_1 10.0.0.99:82 weight 13 check "
"inter 30s fall 3 rise 2 cookie sample_member_id_1\n"
" server sample_member_id_2 10.0.0.98:82 weight 13 check "
"inter 30s fall 3 rise 2 cookie sample_member_id_2\n"
"\n"
"backend sample_pool_id_2\n"
" mode http\n"
" balance roundrobin\n"
" cookie SRV insert indirect nocache\n"
" timeout check 31\n"
" option httpchk GET /healthmon.html\n"
" http-check expect rstatus 418\n"
" option forwardfor\n"
" server sample_member_id_3 10.0.0.97:82 weight 13 check "
"inter 30s fall 3 rise 2 cookie sample_member_id_3\n\n")
rendered_obj = self.jinja_cfg.render_loadbalancer_obj(
sample_configs.sample_listener_tuple(l7=True))
self.assertEqual(sample_configs.sample_base_expected_config(
frontend=fe, backend=be), rendered_obj)
def test_transform_session_persistence(self):
in_persistence = sample_configs.sample_session_persistence_tuple()
ret = self.jinja_cfg._transform_session_persistence(in_persistence)
@ -267,7 +315,7 @@ class TestHaproxyCfg(base.TestCase):
def test_transform_health_monitor(self):
in_persistence = sample_configs.sample_health_monitor_tuple()
ret = self.jinja_cfg._transform_health_monitor(in_persistence)
self.assertEqual(sample_configs.RET_MONITOR, ret)
self.assertEqual(sample_configs.RET_MONITOR_1, ret)
def test_transform_member(self):
in_member = sample_configs.sample_member_tuple('sample_member_id_1',
@ -278,19 +326,57 @@ class TestHaproxyCfg(base.TestCase):
def test_transform_pool(self):
in_pool = sample_configs.sample_pool_tuple()
ret = self.jinja_cfg._transform_pool(in_pool)
self.assertEqual(sample_configs.RET_POOL, ret)
self.assertEqual(sample_configs.RET_POOL_1, ret)
def test_transform_pool_2(self):
in_pool = sample_configs.sample_pool_tuple(sample_pool=2)
ret = self.jinja_cfg._transform_pool(in_pool)
self.assertEqual(sample_configs.RET_POOL_2, ret)
def test_transform_listener(self):
in_listener = sample_configs.sample_listener_tuple()
ret = self.jinja_cfg._transform_listener(in_listener, None)
self.assertEqual(sample_configs.RET_LISTENER, ret)
def test_transform_listener_with_l7(self):
in_listener = sample_configs.sample_listener_tuple(l7=True)
ret = self.jinja_cfg._transform_listener(in_listener, None)
self.assertEqual(sample_configs.RET_LISTENER_L7, ret)
def test_transform_loadbalancer(self):
in_listener = sample_configs.sample_listener_tuple()
ret = self.jinja_cfg._transform_loadbalancer(
in_listener.load_balancer, in_listener, None)
self.assertEqual(sample_configs.RET_LB, ret)
def test_transform_loadbalancer_with_l7(self):
in_listener = sample_configs.sample_listener_tuple(l7=True)
ret = self.jinja_cfg._transform_loadbalancer(
in_listener.load_balancer, in_listener, None)
self.assertEqual(sample_configs.RET_LB_L7, ret)
def test_transform_l7policy(self):
in_l7policy = sample_configs.sample_l7policy_tuple(
'sample_l7policy_id_1')
ret = self.jinja_cfg._transform_l7policy(in_l7policy)
self.assertEqual(sample_configs.RET_L7POLICY_1, ret)
def test_transform_l7policy_2(self):
in_l7policy = sample_configs.sample_l7policy_tuple(
'sample_l7policy_id_2', sample_policy=2)
ret = self.jinja_cfg._transform_l7policy(in_l7policy)
self.assertEqual(sample_configs.RET_L7POLICY_2, ret)
def test_escape_haproxy_config_string(self):
self.assertEqual(self.jinja_cfg._escape_haproxy_config_string(
'string_with_none'), 'string_with_none')
self.assertEqual(self.jinja_cfg._escape_haproxy_config_string(
'string with spaces'), 'string\\ with\\ spaces')
self.assertEqual(self.jinja_cfg._escape_haproxy_config_string(
'string\\with\\backslashes'), 'string\\\\with\\\\backslashes')
self.assertEqual(self.jinja_cfg._escape_haproxy_config_string(
'string\\ with\\ all'), 'string\\\\\\ with\\\\\\ all')
def test_expand_expected_codes(self):
exp_codes = ''
self.assertEqual(self.jinja_cfg._expand_expected_codes(exp_codes),

View File

@ -16,6 +16,9 @@
import collections
from octavia.common import constants
def sample_amphora_tuple():
amphora = collections.namedtuple('amphora', 'id, load_balancer_id, '
'compute_id, status,'
@ -29,7 +32,7 @@ RET_PERSISTENCE = {
'type': 'HTTP_COOKIE',
'cookie_name': None}
RET_MONITOR = {
RET_MONITOR_1 = {
'id': 'sample_monitor_id_1',
'type': 'HTTP',
'delay': 30,
@ -41,6 +44,18 @@ RET_MONITOR = {
'expected_codes': '418',
'enabled': True}
RET_MONITOR_2 = {
'id': 'sample_monitor_id_2',
'type': 'HTTP',
'delay': 30,
'timeout': 31,
'fall_threshold': 3,
'rise_threshold': 2,
'http_method': 'GET',
'url_path': '/healthmon.html',
'expected_codes': '418',
'enabled': True}
RET_MEMBER_1 = {
'id': 'sample_member_id_1',
'address': '10.0.0.99',
@ -59,12 +74,32 @@ RET_MEMBER_2 = {
'enabled': True,
'operating_status': 'ACTIVE'}
RET_POOL = {
RET_MEMBER_3 = {
'id': 'sample_member_id_3',
'address': '10.0.0.97',
'protocol_port': 82,
'weight': 13,
'subnet_id': '10.0.0.1/24',
'enabled': True,
'operating_status': 'ACTIVE'}
RET_POOL_1 = {
'id': 'sample_pool_id_1',
'protocol': 'http',
'lb_algorithm': 'roundrobin',
'members': [RET_MEMBER_1, RET_MEMBER_2],
'health_monitor': RET_MONITOR,
'health_monitor': RET_MONITOR_1,
'session_persistence': RET_PERSISTENCE,
'enabled': True,
'operating_status': 'ACTIVE',
'stick_size': '10k'}
RET_POOL_2 = {
'id': 'sample_pool_id_2',
'protocol': 'http',
'lb_algorithm': 'roundrobin',
'members': [RET_MEMBER_3],
'health_monitor': RET_MONITOR_2,
'session_persistence': RET_PERSISTENCE,
'enabled': True,
'operating_status': 'ACTIVE',
@ -77,41 +112,141 @@ RET_SNI_CONT_1 = {'id': 'cont_id_2', 'allencompassingpem': 'imapem2',
RET_SNI_CONT_2 = {'id': 'cont_id_3', 'allencompassingpem': 'imapem3',
'primary_cn': 'FakeCn2'}
RET_L7RULE_1 = {
'id': 'sample_l7rule_id_1',
'type': constants.L7RULE_TYPE_PATH,
'compare_type': constants.L7RULE_COMPARE_TYPE_STARTS_WITH,
'key': None,
'value': '/api',
'invert': False}
RET_L7RULE_2 = {
'id': 'sample_l7rule_id_2',
'type': constants.L7RULE_TYPE_HEADER,
'compare_type': constants.L7RULE_COMPARE_TYPE_CONTAINS,
'key': 'Some-header',
'value': 'This\\ string\\\\\\ with\\ stuff',
'invert': True}
RET_L7RULE_3 = {
'id': 'sample_l7rule_id_3',
'type': constants.L7RULE_TYPE_COOKIE,
'compare_type': constants.L7RULE_COMPARE_TYPE_REGEX,
'key': 'some-cookie',
'value': 'this.*|that',
'invert': False}
RET_L7RULE_4 = {
'id': 'sample_l7rule_id_4',
'type': constants.L7RULE_TYPE_FILE_TYPE,
'compare_type': constants.L7RULE_COMPARE_TYPE_EQUAL_TO,
'key': None,
'value': 'jpg',
'invert': False}
RET_L7RULE_5 = {
'id': 'sample_l7rule_id_5',
'type': constants.L7RULE_TYPE_HOST_NAME,
'compare_type': constants.L7RULE_COMPARE_TYPE_ENDS_WITH,
'key': None,
'value': '.example.com',
'invert': False}
RET_L7POLICY_1 = {
'id': 'sample_l7policy_id_1',
'action': constants.L7POLICY_ACTION_REDIRECT_TO_POOL,
'redirect_pool': RET_POOL_2,
'redirect_url': None,
'enabled': True,
'l7rules': [RET_L7RULE_1]}
RET_L7POLICY_2 = {
'id': 'sample_l7policy_id_2',
'action': constants.L7POLICY_ACTION_REDIRECT_TO_URL,
'redirect_pool': None,
'redirect_url': 'http://www.example.com',
'enabled': True,
'l7rules': [RET_L7RULE_2, RET_L7RULE_3]}
RET_L7POLICY_3 = {
'id': 'sample_l7policy_id_3',
'action': constants.L7POLICY_ACTION_REJECT,
'redirect_pool': None,
'redirect_url': None,
'enabled': True,
'l7rules': [RET_L7RULE_4, RET_L7RULE_5]}
RET_L7POLICY_4 = {
'id': 'sample_l7policy_id_4',
'action': constants.L7POLICY_ACTION_REJECT,
'redirect_pool': None,
'redirect_url': None,
'enabled': True,
'l7rules': []}
RET_L7POLICY_5 = {
'id': 'sample_l7policy_id_5',
'action': constants.L7POLICY_ACTION_REJECT,
'redirect_pool': None,
'redirect_url': None,
'enabled': False,
'l7rules': [RET_L7RULE_5]}
RET_LISTENER = {
'id': 'sample_listener_id_1',
'protocol_port': '80',
'protocol': 'HTTP',
'protocol_mode': 'http',
'default_pool': RET_POOL,
'default_pool': RET_POOL_1,
'connection_limit': 98,
'amphorae': [sample_amphora_tuple()],
'peer_port': 1024,
'topology': 'SINGLE'}
'topology': 'SINGLE',
'pools': [RET_POOL_1],
'l7policies': []}
RET_LISTENER_L7 = {
'id': 'sample_listener_id_1',
'protocol_port': '80',
'protocol': 'HTTP',
'protocol_mode': 'http',
'default_pool': RET_POOL_1,
'connection_limit': 98,
'amphorae': [sample_amphora_tuple()],
'peer_port': 1024,
'topology': 'SINGLE',
'pools': [RET_POOL_1, RET_POOL_2],
'l7policies': [RET_L7POLICY_1, RET_L7POLICY_2, RET_L7POLICY_3,
RET_L7POLICY_4, RET_L7POLICY_5]}
RET_LISTENER_TLS = {
'id': 'sample_listener_id_1',
'protocol_port': '443',
'protocol': 'TERMINATED_HTTPS',
'protocol_mode': 'http',
'default_pool': RET_POOL,
'default_pool': RET_POOL_1,
'connection_limit': 98,
'tls_certificate_id': 'cont_id_1',
'default_tls_path': '/etc/ssl/sample_loadbalancer_id_1/fakeCN.pem',
'default_tls_container': RET_DEF_TLS_CONT}
'default_tls_container': RET_DEF_TLS_CONT,
'pools': [RET_POOL_1],
'l7policies': []}
RET_LISTENER_TLS_SNI = {
'id': 'sample_listener_id_1',
'protocol_port': '443',
'protocol': 'http',
'protocol': 'TERMINATED_HTTPS',
'default_pool': RET_POOL,
'default_pool': RET_POOL_1,
'connection_limit': 98,
'tls_certificate_id': 'cont_id_1',
'default_tls_path': '/etc/ssl/sample_loadbalancer_id_1/fakeCN.pem',
'default_tls_container': RET_DEF_TLS_CONT,
'crt_dir': '/v2/sample_loadbalancer_id_1',
'sni_container_ids': ['cont_id_2', 'cont_id_3'],
'sni_containers': [RET_SNI_CONT_1, RET_SNI_CONT_2]}
'sni_containers': [RET_SNI_CONT_1, RET_SNI_CONT_2],
'pools': [RET_POOL_1],
'l7policies': []}
RET_LB = {
'name': 'test-lb',
@ -129,10 +264,16 @@ RET_LB_TLS_SNI = {
'vip_address': '10.0.0.2',
'listener': RET_LISTENER_TLS_SNI}
RET_LB_L7 = {
'name': 'test-lb',
'vip_address': '10.0.0.2',
'listener': RET_LISTENER_L7,
'topology': 'SINGLE'}
def sample_loadbalancer_tuple(proto=None, monitor=True, persistence=True,
persistence_type=None, tls=False, sni=False,
topology=None):
topology=None, l7=False):
proto = 'HTTP' if proto is None else proto
topology = 'SINGLE' if topology is None else topology
in_lb = collections.namedtuple(
@ -147,7 +288,8 @@ def sample_loadbalancer_tuple(proto=None, monitor=True, persistence=True,
persistence=persistence,
persistence_type=persistence_type,
tls=tls,
sni=sni)]
sni=sni,
l7=l7)]
)
@ -188,7 +330,8 @@ def sample_vip_tuple():
def sample_listener_tuple(proto=None, monitor=True, persistence=True,
persistence_type=None, persistence_cookie=None,
tls=False, sni=False, peer_port=None, topology=None):
tls=False, sni=False, peer_port=None, topology=None,
l7=False):
proto = 'HTTP' if proto is None else proto
be_proto = 'HTTP' if proto is 'TERMINATED_HTTPS' else proto
topology = 'SINGLE' if topology is None else topology
@ -198,7 +341,31 @@ def sample_listener_tuple(proto=None, monitor=True, persistence=True,
'listener', 'id, project_id, protocol_port, protocol, default_pool, '
'connection_limit, tls_certificate_id, '
'sni_container_ids, default_tls_container, '
'sni_containers, load_balancer, peer_port')
'sni_containers, load_balancer, peer_port, pools, '
'l7policies')
if l7:
pools = [
sample_pool_tuple(
proto=be_proto, monitor=monitor, persistence=persistence,
persistence_type=persistence_type,
persistence_cookie=persistence_cookie),
sample_pool_tuple(
proto=be_proto, monitor=monitor, persistence=persistence,
persistence_type=persistence_type,
persistence_cookie=persistence_cookie, sample_pool=2)]
l7policies = [
sample_l7policy_tuple('sample_l7policy_id_1', sample_policy=1),
sample_l7policy_tuple('sample_l7policy_id_2', sample_policy=2),
sample_l7policy_tuple('sample_l7policy_id_3', sample_policy=3),
sample_l7policy_tuple('sample_l7policy_id_4', sample_policy=4),
sample_l7policy_tuple('sample_l7policy_id_5', sample_policy=5)]
else:
pools = [
sample_pool_tuple(
proto=be_proto, monitor=monitor, persistence=persistence,
persistence_type=persistence_type,
persistence_cookie=persistence_cookie)]
l7policies = []
return in_listener(
id='sample_listener_id_1',
project_id='12345',
@ -233,7 +400,9 @@ def sample_listener_tuple(proto=None, monitor=True, persistence=True,
private_key='--imakey3--\n', intermediates=[
'--imainter3--\n', '--imainter3too--\n'
], primary_cn='aFakeCN'))]
if sni else []
if sni else [],
pools=pools,
l7policies=l7policies
)
@ -258,21 +427,32 @@ def sample_tls_container_tuple(id='cont_id_1', certificate=None,
def sample_pool_tuple(proto=None, monitor=True, persistence=True,
persistence_type=None, persistence_cookie=None):
persistence_type=None, persistence_cookie=None,
sample_pool=1):
proto = 'HTTP' if proto is None else proto
in_pool = collections.namedtuple(
'pool', 'id, protocol, lb_algorithm, members, health_monitor,'
'session_persistence, enabled, operating_status')
mon = sample_health_monitor_tuple(proto=proto) if monitor is True else None
persis = sample_session_persistence_tuple(
persistence_type=persistence_type,
persistence_cookie=persistence_cookie) if persistence is True else None
mon = None
if sample_pool == 1:
id = 'sample_pool_id_1'
members = [sample_member_tuple('sample_member_id_1', '10.0.0.99'),
sample_member_tuple('sample_member_id_2', '10.0.0.98')]
if monitor is True:
mon = sample_health_monitor_tuple(proto=proto)
elif sample_pool == 2:
id = 'sample_pool_id_2'
members = [sample_member_tuple('sample_member_id_3', '10.0.0.97')]
if monitor is True:
mon = sample_health_monitor_tuple(proto=proto, sample_hm=2)
return in_pool(
id='sample_pool_id_1',
id=id,
protocol=proto,
lb_algorithm='ROUND_ROBIN',
members=[sample_member_tuple('sample_member_id_1', '10.0.0.99'),
sample_member_tuple('sample_member_id_2', '10.0.0.98')],
members=members,
health_monitor=mon,
session_persistence=persis,
enabled=True,
@ -303,18 +483,103 @@ def sample_session_persistence_tuple(persistence_type=None,
cookie_name=persistence_cookie)
def sample_health_monitor_tuple(proto='HTTP'):
def sample_health_monitor_tuple(proto='HTTP', sample_hm=1):
proto = 'HTTP' if proto is 'TERMINATED_HTTPS' else proto
monitor = collections.namedtuple(
'monitor', 'id, type, delay, timeout, fall_threshold, rise_threshold,'
'http_method, url_path, expected_codes, enabled')
return monitor(id='sample_monitor_id_1', type=proto, delay=30,
if sample_hm == 1:
id = 'sample_monitor_id_1'
url_path = '/index.html'
elif sample_hm == 2:
id = 'sample_monitor_id_2'
url_path = '/healthmon.html'
return monitor(id=id, type=proto, delay=30,
timeout=31, fall_threshold=3, rise_threshold=2,
http_method='GET', url_path='/index.html',
http_method='GET', url_path=url_path,
expected_codes='418', enabled=True)
def sample_l7policy_tuple(id,
action=constants.L7POLICY_ACTION_REJECT,
redirect_pool=None, redirect_url=None,
enabled=True, sample_policy=1):
in_l7policy = collections.namedtuple('l7policy',
'id, action, redirect_pool, '
'redirect_url, l7rules, enabled')
if sample_policy == 1:
action = constants.L7POLICY_ACTION_REDIRECT_TO_POOL
redirect_pool = sample_pool_tuple(sample_pool=2)
l7rules = [sample_l7rule_tuple('sample_l7rule_id_1')]
elif sample_policy == 2:
action = constants.L7POLICY_ACTION_REDIRECT_TO_URL
redirect_url = 'http://www.example.com'
l7rules = [sample_l7rule_tuple('sample_l7rule_id_2', sample_rule=2),
sample_l7rule_tuple('sample_l7rule_id_3', sample_rule=3)]
elif sample_policy == 3:
action = constants.L7POLICY_ACTION_REJECT
l7rules = [sample_l7rule_tuple('sample_l7rule_id_4', sample_rule=4),
sample_l7rule_tuple('sample_l7rule_id_5', sample_rule=5)]
elif sample_policy == 4:
action = constants.L7POLICY_ACTION_REJECT
l7rules = []
elif sample_policy == 5:
action = constants.L7POLICY_ACTION_REJECT
enabled = False
l7rules = [sample_l7rule_tuple('sample_l7rule_id_5', sample_rule=5)]
return in_l7policy(
id=id,
action=action,
redirect_pool=redirect_pool,
redirect_url=redirect_url,
l7rules=l7rules,
enabled=enabled)
def sample_l7rule_tuple(id,
type=constants.L7RULE_TYPE_PATH,
compare_type=constants.L7RULE_COMPARE_TYPE_STARTS_WITH,
key=None,
value='/api',
invert=False,
sample_rule=1):
in_l7rule = collections.namedtuple('l7rule',
'id, type, compare_type, '
'key, value, invert')
if sample_rule == 2:
type = constants.L7RULE_TYPE_HEADER
compare_type = constants.L7RULE_COMPARE_TYPE_CONTAINS
key = 'Some-header'
value = 'This string\\ with stuff'
invert = True
if sample_rule == 3:
type = constants.L7RULE_TYPE_COOKIE
compare_type = constants.L7RULE_COMPARE_TYPE_REGEX
key = 'some-cookie'
value = 'this.*|that'
invert = False
if sample_rule == 4:
type = constants.L7RULE_TYPE_FILE_TYPE
compare_type = constants.L7RULE_COMPARE_TYPE_EQUAL_TO
key = None
value = 'jpg'
invert = False
if sample_rule == 5:
type = constants.L7RULE_TYPE_HOST_NAME
compare_type = constants.L7RULE_COMPARE_TYPE_ENDS_WITH
key = None
value = '.example.com'
invert = False
return in_l7rule(
id=id,
type=type,
compare_type=compare_type,
key=key,
value=value,
invert=invert)
def sample_base_expected_config(frontend=None, backend=None, peers=None):
if frontend is None:
frontend = ("frontend sample_listener_id_1\n"