From 2fabcabc4eb0b305332d749a8cb1bb56205b96fa Mon Sep 17 00:00:00 2001 From: Stephen Balukoff Date: Tue, 9 Feb 2016 22:16:19 -0800 Subject: [PATCH] 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 --- .../jinja/templates/haproxy_listener.template | 35 -- .../jinja/templates/haproxy_proxies.template | 114 ------- .../drivers/haproxy/rest_api_driver.py | 2 +- .../jinja/haproxy}/__init__.py | 0 .../jinja/haproxy}/jinja_cfg.py | 54 ++- .../jinja/haproxy}/templates/__init__.py | 0 .../jinja/haproxy/templates/base.j2} | 2 +- .../jinja/haproxy/templates/haproxy.cfg.j2} | 27 +- .../common/jinja/haproxy/templates/macros.j2 | 204 ++++++++++++ .../agent/api_server/test_listener.py | 2 +- .../jinja/haproxy}/__init__.py | 0 .../jinja/haproxy}/test_jinja_cfg.py | 94 +++++- .../common/sample_configs/sample_configs.py | 309 ++++++++++++++++-- 13 files changed, 652 insertions(+), 191 deletions(-) delete mode 100644 octavia/amphorae/drivers/haproxy/jinja/templates/haproxy_listener.template delete mode 100644 octavia/amphorae/drivers/haproxy/jinja/templates/haproxy_proxies.template rename octavia/{amphorae/drivers/haproxy/jinja => common/jinja/haproxy}/__init__.py (100%) rename octavia/{amphorae/drivers/haproxy/jinja => common/jinja/haproxy}/jinja_cfg.py (85%) rename octavia/{amphorae/drivers/haproxy/jinja => common/jinja/haproxy}/templates/__init__.py (100%) rename octavia/{amphorae/drivers/haproxy/jinja/templates/haproxy_base.template => common/jinja/haproxy/templates/base.j2} (97%) rename octavia/{amphorae/drivers/haproxy/jinja/templates/haproxy_loadbalancer.template => common/jinja/haproxy/templates/haproxy.cfg.j2} (62%) create mode 100644 octavia/common/jinja/haproxy/templates/macros.j2 rename octavia/tests/unit/{amphorae/drivers/haproxy/jinja => common/jinja/haproxy}/__init__.py (100%) rename octavia/tests/unit/{amphorae/drivers/haproxy/jinja => common/jinja/haproxy}/test_jinja_cfg.py (76%) diff --git a/octavia/amphorae/drivers/haproxy/jinja/templates/haproxy_listener.template b/octavia/amphorae/drivers/haproxy/jinja/templates/haproxy_listener.template deleted file mode 100644 index 8527624198..0000000000 --- a/octavia/amphorae/drivers/haproxy/jinja/templates/haproxy_listener.template +++ /dev/null @@ -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 %} diff --git a/octavia/amphorae/drivers/haproxy/jinja/templates/haproxy_proxies.template b/octavia/amphorae/drivers/haproxy/jinja/templates/haproxy_proxies.template deleted file mode 100644 index f3867449e0..0000000000 --- a/octavia/amphorae/drivers/haproxy/jinja/templates/haproxy_proxies.template +++ /dev/null @@ -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 %} diff --git a/octavia/amphorae/drivers/haproxy/rest_api_driver.py b/octavia/amphorae/drivers/haproxy/rest_api_driver.py index 25626a4aa9..ce0e9193c1 100644 --- a/octavia/amphorae/drivers/haproxy/rest_api_driver.py +++ b/octavia/amphorae/drivers/haproxy/rest_api_driver.py @@ -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 diff --git a/octavia/amphorae/drivers/haproxy/jinja/__init__.py b/octavia/common/jinja/haproxy/__init__.py similarity index 100% rename from octavia/amphorae/drivers/haproxy/jinja/__init__.py rename to octavia/common/jinja/haproxy/__init__.py diff --git a/octavia/amphorae/drivers/haproxy/jinja/jinja_cfg.py b/octavia/common/jinja/haproxy/jinja_cfg.py similarity index 85% rename from octavia/amphorae/drivers/haproxy/jinja/jinja_cfg.py rename to octavia/common/jinja/haproxy/jinja_cfg.py index 26a148295b..88609773e9 100644 --- a/octavia/amphorae/drivers/haproxy/jinja/jinja_cfg.py +++ b/octavia/common/jinja/haproxy/jinja_cfg.py @@ -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. diff --git a/octavia/amphorae/drivers/haproxy/jinja/templates/__init__.py b/octavia/common/jinja/haproxy/templates/__init__.py similarity index 100% rename from octavia/amphorae/drivers/haproxy/jinja/templates/__init__.py rename to octavia/common/jinja/haproxy/templates/__init__.py diff --git a/octavia/amphorae/drivers/haproxy/jinja/templates/haproxy_base.template b/octavia/common/jinja/haproxy/templates/base.j2 similarity index 97% rename from octavia/amphorae/drivers/haproxy/jinja/templates/haproxy_base.template rename to octavia/common/jinja/haproxy/templates/base.j2 index fda1be9b41..b035a003a4 100644 --- a/octavia/amphorae/drivers/haproxy/jinja/templates/haproxy_base.template +++ b/octavia/common/jinja/haproxy/templates/base.j2 @@ -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 diff --git a/octavia/amphorae/drivers/haproxy/jinja/templates/haproxy_loadbalancer.template b/octavia/common/jinja/haproxy/templates/haproxy.cfg.j2 similarity index 62% rename from octavia/amphorae/drivers/haproxy/jinja/templates/haproxy_loadbalancer.template rename to octavia/common/jinja/haproxy/templates/haproxy.cfg.j2 index c74fb25b2b..1b4214ae05 100644 --- a/octavia/amphorae/drivers/haproxy/jinja/templates/haproxy_loadbalancer.template +++ b/octavia/common/jinja/haproxy/templates/haproxy.cfg.j2 @@ -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 %} \ No newline at end of file + {{- frontend_macro(constants, loadbalancer.listener, + loadbalancer.vip_address) }} + {% for pool in loadbalancer.listener.pools %} + {{- backend_macro(constants, loadbalancer.listener, pool) }} + {% endfor %} +{% endblock proxies %} diff --git a/octavia/common/jinja/haproxy/templates/macros.j2 b/octavia/common/jinja/haproxy/templates/macros.j2 new file mode 100644 index 0000000000..8d4315d04c --- /dev/null +++ b/octavia/common/jinja/haproxy/templates/macros.j2 @@ -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 %} diff --git a/octavia/tests/unit/amphorae/backends/agent/api_server/test_listener.py b/octavia/tests/unit/amphorae/backends/agent/api_server/test_listener.py index 8b599a87d8..aca9883539 100644 --- a/octavia/tests/unit/amphorae/backends/agent/api_server/test_listener.py +++ b/octavia/tests/unit/amphorae/backends/agent/api_server/test_listener.py @@ -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 diff --git a/octavia/tests/unit/amphorae/drivers/haproxy/jinja/__init__.py b/octavia/tests/unit/common/jinja/haproxy/__init__.py similarity index 100% rename from octavia/tests/unit/amphorae/drivers/haproxy/jinja/__init__.py rename to octavia/tests/unit/common/jinja/haproxy/__init__.py diff --git a/octavia/tests/unit/amphorae/drivers/haproxy/jinja/test_jinja_cfg.py b/octavia/tests/unit/common/jinja/haproxy/test_jinja_cfg.py similarity index 76% rename from octavia/tests/unit/amphorae/drivers/haproxy/jinja/test_jinja_cfg.py rename to octavia/tests/unit/common/jinja/haproxy/test_jinja_cfg.py index 9ed0ce6605..a2fa6e8ebb 100644 --- a/octavia/tests/unit/amphorae/drivers/haproxy/jinja/test_jinja_cfg.py +++ b/octavia/tests/unit/common/jinja/haproxy/test_jinja_cfg.py @@ -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), diff --git a/octavia/tests/unit/common/sample_configs/sample_configs.py b/octavia/tests/unit/common/sample_configs/sample_configs.py index 73c7225d00..70a5768756 100644 --- a/octavia/tests/unit/common/sample_configs/sample_configs.py +++ b/octavia/tests/unit/common/sample_configs/sample_configs.py @@ -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"