summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPhillip Toohill <phillip.toohill@rackspace.com>2014-07-14 15:32:17 -0500
committerDoug Wiegley <dougw@a10networks.com>2014-11-06 17:55:35 +0100
commit7147389bad4fb76d7794f7b406608e0631cec4d8 (patch)
treed0b3a5384580f62197fb47582f88e7e19daf2d41
parentcfa4a86ebe9c628a14f7c4dc34986e72b1504bf0 (diff)
downloadneutron-feature/lbaasv2.tar.gz
Implement Jinja templates for haproxy configfeature/lbaasv2
Added templates dir Added haproxy v1.4 template Added template tests Added jinja_cfg for new haproxy jinja templating Partially-implements: blueprint lbaas-api-and-objmodel-improvement Partially-implements: blueprint lbaas-refactor-haproxy-namespace-driver-to-new-driver-interface Change-Id: Iad3a7be0146e1148b29edee56233aa3196c982dc Co-authored-by: Brandon Logan <brandon.logan@rackspace.com> Co-authored-by: Phillip Toohill <phillip.toohill@rackspace.com> Co-authored-by: Dustin Lundquist <dustin@null-ptr.net> Co-authored-by: Doug Wiegley <dougw@a10networks.com>
-rw-r--r--etc/services.conf3
-rw-r--r--neutron/services/loadbalancer/drivers/haproxy/jinja_cfg.py198
-rw-r--r--neutron/services/loadbalancer/drivers/haproxy/templates/__init__.py0
-rw-r--r--neutron/services/loadbalancer/drivers/haproxy/templates/haproxy_base.template33
-rw-r--r--neutron/services/loadbalancer/drivers/haproxy/templates/haproxy_v1.4.template29
-rw-r--r--neutron/services/loadbalancer/drivers/haproxy/templates/haproxy_v1.4_proxies.template74
-rw-r--r--neutron/tests/unit/services/loadbalancer/drivers/haproxy/sample_configs/__init__.py0
-rw-r--r--neutron/tests/unit/services/loadbalancer/drivers/haproxy/sample_configs/sample_configs.py207
-rw-r--r--neutron/tests/unit/services/loadbalancer/drivers/haproxy/test_jinja_cfg.py234
9 files changed, 778 insertions, 0 deletions
diff --git a/etc/services.conf b/etc/services.conf
index f8a6090055..262c120827 100644
--- a/etc/services.conf
+++ b/etc/services.conf
@@ -38,3 +38,6 @@
#async_requests =
#lb_flavor = small
#sync_interval = 60
+
+[haproxy]
+#jinja_config_template = /opt/stack/neutron/neutron/services/drivers/haproxy/templates/haproxy_v1.4.template
diff --git a/neutron/services/loadbalancer/drivers/haproxy/jinja_cfg.py b/neutron/services/loadbalancer/drivers/haproxy/jinja_cfg.py
new file mode 100644
index 0000000000..cf7d8ff5f2
--- /dev/null
+++ b/neutron/services/loadbalancer/drivers/haproxy/jinja_cfg.py
@@ -0,0 +1,198 @@
+# Copyright 2014 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
+import six
+
+from neutron.agent.linux import utils
+from neutron.plugins.common import constants as plugin_constants
+from neutron.services.loadbalancer import constants
+from oslo.config import cfg
+
+PROTOCOL_MAP = {
+ constants.PROTOCOL_TCP: 'tcp',
+ constants.PROTOCOL_HTTP: 'http',
+ constants.PROTOCOL_HTTPS: 'tcp'
+}
+
+BALANCE_MAP = {
+ constants.LB_METHOD_ROUND_ROBIN: 'roundrobin',
+ constants.LB_METHOD_LEAST_CONNECTIONS: 'leastconn',
+ constants.LB_METHOD_SOURCE_IP: 'source'
+}
+
+STATS_MAP = {
+ constants.STATS_ACTIVE_CONNECTIONS: 'scur',
+ constants.STATS_MAX_CONNECTIONS: 'smax',
+ constants.STATS_CURRENT_SESSIONS: 'scur',
+ constants.STATS_MAX_SESSIONS: 'smax',
+ constants.STATS_TOTAL_CONNECTIONS: 'stot',
+ constants.STATS_TOTAL_SESSIONS: 'stot',
+ constants.STATS_IN_BYTES: 'bin',
+ constants.STATS_OUT_BYTES: 'bout',
+ constants.STATS_CONNECTION_ERRORS: 'econ',
+ constants.STATS_RESPONSE_ERRORS: 'eresp'
+}
+
+ACTIVE_PENDING_STATUSES = plugin_constants.ACTIVE_PENDING_STATUSES + (
+ plugin_constants.INACTIVE, plugin_constants.DEFERRED)
+
+TEMPLATES_DIR = os.path.abspath(
+ os.path.join(os.path.dirname(__file__), 'templates/'))
+JINJA_ENV = None
+
+jinja_opts = [
+ cfg.StrOpt(
+ 'jinja_config_template',
+ default=os.path.join(
+ TEMPLATES_DIR,
+ 'haproxy_v1.4.template'),
+ help=_('Jinja template file for haproxy configuration'))
+]
+
+cfg.CONF.register_opts(jinja_opts, 'haproxy')
+
+
+def save_config(conf_path, loadbalancer, socket_path=None,
+ user_group='nogroup'):
+ """Convert a logical configuration to the HAProxy version."""
+ config_str = render_loadbalancer_obj(loadbalancer, user_group, socket_path)
+ utils.replace_file(conf_path, config_str)
+
+
+def _get_template():
+ global JINJA_ENV
+ if not JINJA_ENV:
+ template_loader = jinja2.FileSystemLoader(
+ searchpath=os.path.dirname(cfg.CONF.haproxy.jinja_config_template))
+ JINJA_ENV = jinja2.Environment(
+ loader=template_loader, trim_blocks=True, lstrip_blocks=True)
+ return JINJA_ENV.get_template(os.path.basename(
+ cfg.CONF.haproxy.jinja_config_template))
+
+
+def render_loadbalancer_obj(loadbalancer, user_group, socket_path):
+ loadbalancer = _transform_loadbalancer(loadbalancer)
+ return _get_template().render({'loadbalancer': loadbalancer,
+ 'user_group': user_group,
+ 'stats_sock': socket_path},
+ constants=constants)
+
+
+def _transform_loadbalancer(loadbalancer):
+ listeners = [_transform_listener(x) for x in loadbalancer.listeners]
+ return {
+ 'name': loadbalancer.name,
+ 'vip_address': loadbalancer.vip_address,
+ 'listeners': listeners
+ }
+
+
+def _transform_listener(listener):
+ ret_value = {
+ 'id': listener.id,
+ 'protocol_port': listener.protocol_port,
+ 'protocol': PROTOCOL_MAP[listener.protocol]
+ }
+ if listener.connection_limit and listener.connection_limit > -1:
+ ret_value['connection_limit'] = listener.connection_limit
+ if listener.default_pool:
+ ret_value['default_pool'] = _transform_pool(listener.default_pool)
+
+ return ret_value
+
+
+def _transform_pool(pool):
+ ret_value = {
+ 'id': pool.id,
+ 'protocol': PROTOCOL_MAP[pool.protocol],
+ 'lb_algorithm': BALANCE_MAP.get(pool.lb_algorithm, 'roundrobin'),
+ 'members': [],
+ 'health_monitor': '',
+ 'session_persistence': '',
+ 'admin_state_up': pool.admin_state_up,
+ 'status': pool.status
+ }
+ members = [_transform_member(x)
+ for x in pool.members if _include_member(x)]
+ ret_value['members'] = members
+ if pool.healthmonitor:
+ ret_value['health_monitor'] = _transform_health_monitor(
+ pool.healthmonitor)
+ if pool.sessionpersistence:
+ ret_value['session_persistence'] = _transform_session_persistence(
+ pool.sessionpersistence)
+ return ret_value
+
+
+def _transform_session_persistence(persistence):
+ return {
+ 'type': persistence.type,
+ 'cookie_name': persistence.cookie_name
+ }
+
+
+def _transform_member(member):
+ return {
+ 'id': member.id,
+ 'address': member.address,
+ 'protocol_port': member.protocol_port,
+ 'weight': member.weight,
+ 'admin_state_up': member.admin_state_up,
+ 'subnet_id': member.subnet_id,
+ 'status': member.status
+ }
+
+
+def _transform_health_monitor(monitor):
+ return {
+ 'id': monitor.id,
+ 'type': monitor.type,
+ 'delay': monitor.delay,
+ 'timeout': monitor.timeout,
+ 'max_retries': monitor.max_retries,
+ 'http_method': monitor.http_method,
+ 'url_path': monitor.url_path,
+ 'expected_codes': '|'.join(
+ _expand_expected_codes(monitor.expected_codes)),
+ 'admin_state_up': monitor.admin_state_up,
+ }
+
+
+def _include_member(member):
+ return member.status in ACTIVE_PENDING_STATUSES and member.admin_state_up
+
+
+def _expand_expected_codes(codes):
+ """Expand the expected code string in set of codes.
+
+ 200-204 -> 200, 201, 202, 204
+ 200, 203 -> 200, 203
+ """
+
+ retval = set()
+ for code in codes.replace(',', ' ').split(' '):
+ code = code.strip()
+
+ if not code:
+ continue
+ elif '-' in code:
+ low, hi = code.split('-')[:2]
+ retval.update(
+ str(i) for i in six.moves.xrange(int(low), int(hi) + 1))
+ else:
+ retval.add(code)
+ return retval
diff --git a/neutron/services/loadbalancer/drivers/haproxy/templates/__init__.py b/neutron/services/loadbalancer/drivers/haproxy/templates/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/neutron/services/loadbalancer/drivers/haproxy/templates/__init__.py
diff --git a/neutron/services/loadbalancer/drivers/haproxy/templates/haproxy_base.template b/neutron/services/loadbalancer/drivers/haproxy/templates/haproxy_base.template
new file mode 100644
index 0000000000..e405c959b5
--- /dev/null
+++ b/neutron/services/loadbalancer/drivers/haproxy/templates/haproxy_base.template
@@ -0,0 +1,33 @@
+{# # Copyright 2014 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.
+#
+#}
+# Configuration for {{ loadbalancer_name }}
+global
+ daemon
+ user nobody
+ group {{ usergroup }}
+ log /dev/log local0
+ log /dev/log local1 notice
+ stats socket {{ sock_path }} mode 0666 level user
+
+defaults
+ log global
+ retries 3
+ option redispatch
+ timeout connect 5000
+ timeout client 50000
+ timeout server 50000
+
+{% block proxies %}{% endblock proxies %}
diff --git a/neutron/services/loadbalancer/drivers/haproxy/templates/haproxy_v1.4.template b/neutron/services/loadbalancer/drivers/haproxy/templates/haproxy_v1.4.template
new file mode 100644
index 0000000000..60da1da694
--- /dev/null
+++ b/neutron/services/loadbalancer/drivers/haproxy/templates/haproxy_v1.4.template
@@ -0,0 +1,29 @@
+{# # Copyright 2014 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.
+#
+#}
+{% extends 'haproxy_v1.4_proxies.template' %}
+{% set loadbalancer_name = loadbalancer.name %}
+{% set usergroup = user_group %}
+{% set sock_path = stats_sock %}
+
+{% block proxies %}
+{% from 'haproxy_v1.4_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
diff --git a/neutron/services/loadbalancer/drivers/haproxy/templates/haproxy_v1.4_proxies.template b/neutron/services/loadbalancer/drivers/haproxy/templates/haproxy_v1.4_proxies.template
new file mode 100644
index 0000000000..7c69fa0f25
--- /dev/null
+++ b/neutron/services/loadbalancer/drivers/haproxy/templates/haproxy_v1.4_proxies.template
@@ -0,0 +1,74 @@
+{# # Copyright 2014 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.
+#
+#}
+{% extends 'haproxy_base.template' %}
+
+{% 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 == constants.PROTOCOL_HTTP.lower() %}
+ option forwardfor
+{% endif %}
+ bind {{ lb_vip_address }}:{{ listener.protocol_port }}
+ mode {{ listener.protocol }}
+{% 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 %}
+ stick-table type ip size 10k
+ stick on src
+{% elif pool.session_persistence.type == constants.SESSION_PERSISTENCE_HTTP_COOKIE %}
+ cookie SRV insert indirect nocache
+{% elif pool.session_persistence.type == constants.SESSION_PERSISTENCE_APP_COOKIE and pool.session_persistence.cookie_name %}
+ appsession {{ pool.session_persistence.cookie_name }} len 56 timeout 3h
+{% 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 listener.protocol == constants.PROTOCOL_HTTP.lower() %}
+ option forwardfor
+{% endif %}
+{% for member in pool.members %}
+{% if pool.health_monitor %}
+{% set hm_opt = " check inter %ds fall %d"|format(pool.health_monitor.delay, pool.health_monitor.max_retries) %}
+{% 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 %} \ No newline at end of file
diff --git a/neutron/tests/unit/services/loadbalancer/drivers/haproxy/sample_configs/__init__.py b/neutron/tests/unit/services/loadbalancer/drivers/haproxy/sample_configs/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/neutron/tests/unit/services/loadbalancer/drivers/haproxy/sample_configs/__init__.py
diff --git a/neutron/tests/unit/services/loadbalancer/drivers/haproxy/sample_configs/sample_configs.py b/neutron/tests/unit/services/loadbalancer/drivers/haproxy/sample_configs/sample_configs.py
new file mode 100644
index 0000000000..1756d61cf9
--- /dev/null
+++ b/neutron/tests/unit/services/loadbalancer/drivers/haproxy/sample_configs/sample_configs.py
@@ -0,0 +1,207 @@
+# Copyright 2014 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 collections
+
+RET_PERSISTENCE = {
+ 'type': 'HTTP_COOKIE',
+ 'cookie_name': 'HTTP_COOKIE'}
+
+RET_MONITOR = {
+ 'id': 'sample_monitor_id_1',
+ 'type': 'HTTP',
+ 'delay': 30,
+ 'timeout': 31,
+ 'max_retries': 3,
+ 'http_method': 'GET',
+ 'url_path': '/index.html',
+ 'expected_codes': '405|404|500',
+ 'admin_state_up': 'true'}
+
+RET_MEMBER_1 = {
+ 'id': 'sample_member_id_1',
+ 'address': '10.0.0.99',
+ 'protocol_port': 82,
+ 'weight': 13,
+ 'subnet_id': '10.0.0.1/24',
+ 'admin_state_up': 'true',
+ 'status': 'ACTIVE'}
+
+RET_MEMBER_2 = {
+ 'id': 'sample_member_id_2',
+ 'address': '10.0.0.98',
+ 'protocol_port': 82,
+ 'weight': 13,
+ 'subnet_id': '10.0.0.1/24',
+ 'admin_state_up': 'true',
+ 'status': 'ACTIVE'}
+
+RET_POOL = {
+ 'id': 'sample_pool_id_1',
+ 'protocol': 'http',
+ 'lb_algorithm': 'roundrobin',
+ 'members': [RET_MEMBER_1, RET_MEMBER_2],
+ 'health_monitor': RET_MONITOR,
+ 'session_persistence': RET_PERSISTENCE,
+ 'admin_state_up': 'true',
+ 'status': 'ACTIVE'}
+
+RET_LISTENER = {
+ 'id': 'sample_listener_id_1',
+ 'protocol_port': 80,
+ 'protocol': 'http',
+ 'default_pool': RET_POOL,
+ 'connection_limit': 98}
+
+RET_LB = {
+ 'name': 'test-lb',
+ 'vip_address': '10.0.0.2',
+ 'listeners': [RET_LISTENER]}
+
+
+def sample_loadbalancer_tuple(proto=None, monitor=True, persistence=True,
+ persistence_type=None):
+ proto = 'HTTP' if proto is None else proto
+ in_lb = collections.namedtuple(
+ 'loadbalancer', 'id, name, vip_address, protocol, vip_port, '
+ 'listeners')
+ return in_lb(
+ id='sample_loadbalancer_id_1',
+ name='test-lb',
+ vip_address='10.0.0.2',
+ protocol=proto,
+ vip_port=sample_vip_port_tuple(),
+ listeners=[sample_listener_tuple(proto=proto, monitor=monitor,
+ persistence=persistence,
+ persistence_type=persistence_type)]
+ )
+
+
+def sample_vip_port_tuple():
+ vip_port = collections.namedtuple('vip_port', 'fixed_ips')
+ ip_address = collections.namedtuple('ip_address', 'ip_address')
+ in_address = ip_address(ip_address='10.0.0.2')
+ return vip_port(fixed_ips=[in_address])
+
+
+def sample_listener_tuple(proto=None, monitor=True, persistence=True,
+ persistence_type=None):
+ proto = 'HTTP' if proto is None else proto
+ in_listener = collections.namedtuple(
+ 'listener', 'id, protocol_port, protocol, default_pool, '
+ 'connection_limit')
+ return in_listener(
+ id='sample_listener_id_1',
+ protocol_port=80,
+ protocol=proto,
+ default_pool=sample_pool_tuple(proto=proto, monitor=monitor,
+ persistence=persistence,
+ persistence_type=persistence_type),
+ connection_limit=98
+ )
+
+
+def sample_pool_tuple(proto=None, monitor=True, persistence=True,
+ persistence_type=None):
+ proto = 'HTTP' if proto is None else proto
+ in_pool = collections.namedtuple(
+ 'pool', 'id, protocol, lb_algorithm, members, healthmonitor,'
+ 'sessionpersistence, admin_state_up, status')
+ mon = sample_health_monitor_tuple(proto=proto) if monitor is True else None
+ persis = sample_session_persistence_tuple(
+ persistence_type=persistence_type) if persistence is True else None
+ return in_pool(
+ id='sample_pool_id_1',
+ 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')],
+ healthmonitor=mon,
+ sessionpersistence=persis,
+ admin_state_up='true',
+ status='ACTIVE')
+
+
+def sample_member_tuple(id, ip):
+ in_member = collections.namedtuple('member',
+ 'id, address, protocol_port, '
+ 'weight, subnet_id, '
+ 'admin_state_up, status')
+ return in_member(
+ id=id,
+ address=ip,
+ protocol_port=82,
+ weight=13,
+ subnet_id='10.0.0.1/24',
+ admin_state_up='true',
+ status='ACTIVE')
+
+
+def sample_session_persistence_tuple(persistence_type=None):
+ spersistence = collections.namedtuple('SessionPersistence',
+ 'type, cookie_name')
+ pt = 'HTTP_COOKIE' if persistence_type is None else persistence_type
+ return spersistence(type=pt,
+ cookie_name=pt)
+
+
+def sample_health_monitor_tuple(proto=None):
+ proto = 'HTTP' if proto is None else proto
+ monitor = collections.namedtuple(
+ 'monitor', 'id, type, delay, timeout, max_retries, http_method, '
+ 'url_path, expected_codes, admin_state_up')
+
+ return monitor(id='sample_monitor_id_1', type=proto, delay=30,
+ timeout=31, max_retries=3, http_method='GET',
+ url_path='/index.html', expected_codes='500, 405, 404',
+ admin_state_up='true')
+
+
+def sample_base_expected_config(frontend=None, backend=None):
+ if frontend is None:
+ frontend = ("frontend sample_listener_id_1\n"
+ " option tcplog\n"
+ " maxconn 98\n"
+ " option forwardfor\n"
+ " bind 10.0.0.2:80\n"
+ " mode http\n"
+ " default_backend sample_pool_id_1\n\n")
+ if backend is None:
+ backend = ("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 405|404|500\n"
+ " server sample_member_id_1 10.0.0.99:82 weight 13 "
+ "check inter 30s fall 3 cookie sample_member_id_1\n"
+ " server sample_member_id_2 10.0.0.98:82 weight 13 "
+ "check inter 30s fall 3 cookie sample_member_id_2\n")
+ return ("# Configuration for test-lb\n"
+ "global\n"
+ " daemon\n"
+ " user nobody\n"
+ " group nogroup\n"
+ " log /dev/log local0\n"
+ " log /dev/log local1 notice\n"
+ " stats socket /sock_path mode 0666 level user\n\n"
+ "defaults\n"
+ " log global\n"
+ " retries 3\n"
+ " option redispatch\n"
+ " timeout connect 5000\n"
+ " timeout client 50000\n"
+ " timeout server 50000\n\n" + frontend + backend)
diff --git a/neutron/tests/unit/services/loadbalancer/drivers/haproxy/test_jinja_cfg.py b/neutron/tests/unit/services/loadbalancer/drivers/haproxy/test_jinja_cfg.py
new file mode 100644
index 0000000000..7bb87a55d8
--- /dev/null
+++ b/neutron/tests/unit/services/loadbalancer/drivers/haproxy/test_jinja_cfg.py
@@ -0,0 +1,234 @@
+# Copyright 2014 OpenStack Foundation
+# All Rights Reserved.
+#
+# 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 contextlib
+import mock
+
+from neutron.services.loadbalancer.drivers.haproxy import jinja_cfg
+from neutron.tests import base
+from neutron.tests.unit.services.loadbalancer.drivers.haproxy.sample_configs \
+ import sample_configs
+
+
+class TestHaproxyCfg(base.BaseTestCase):
+ def test_save_config(self):
+ with contextlib.nested(
+ mock.patch('neutron.services.loadbalancer.'
+ 'drivers.haproxy.jinja_cfg.render_loadbalancer_obj'),
+ mock.patch('neutron.agent.linux.utils.replace_file')
+ ) as (r_t, replace):
+ r_t.return_value = 'fake_rendered_template'
+ lb = mock.Mock()
+ jinja_cfg.save_config('test_conf_path', lb, 'test_sock_path')
+ r_t.assert_called_once_with(lb, 'nogroup', 'test_sock_path')
+ replace.assert_called_once_with('test_conf_path',
+ 'fake_rendered_template')
+
+ def test_get_template_v14(self):
+ template = jinja_cfg._get_template()
+ self.assertEqual('haproxy_v1.4.template', template.name)
+
+ def test_render_template_http(self):
+ 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 405|404|500\n"
+ " option forwardfor\n"
+ " server sample_member_id_1 10.0.0.99:82 weight 13 check "
+ "inter 30s fall 3 cookie sample_member_id_1\n"
+ " server sample_member_id_2 10.0.0.98:82 weight 13 check "
+ "inter 30s fall 3 cookie sample_member_id_2\n\n")
+ rendered_obj = jinja_cfg.render_loadbalancer_obj(
+ sample_configs.sample_loadbalancer_tuple(),
+ 'nogroup', '/sock_path')
+ self.assertEqual(
+ sample_configs.sample_base_expected_config(backend=be),
+ rendered_obj)
+
+ def test_render_template_https(self):
+ fe = ("frontend sample_listener_id_1\n"
+ " option tcplog\n"
+ " maxconn 98\n"
+ " bind 10.0.0.2:80\n"
+ " mode tcp\n"
+ " default_backend sample_pool_id_1\n\n")
+ be = ("backend sample_pool_id_1\n"
+ " mode tcp\n"
+ " balance roundrobin\n"
+ " cookie SRV insert indirect nocache\n"
+ " timeout check 31\n"
+ " option httpchk GET /index.html\n"
+ " http-check expect rstatus 405|404|500\n"
+ " option ssl-hello-chk\n"
+ " server sample_member_id_1 10.0.0.99:82 weight 13 check "
+ "inter 30s fall 3 cookie sample_member_id_1\n"
+ " server sample_member_id_2 10.0.0.98:82 weight 13 check "
+ "inter 30s fall 3 cookie sample_member_id_2\n\n")
+ rendered_obj = jinja_cfg.render_loadbalancer_obj(
+ sample_configs.sample_loadbalancer_tuple(proto='HTTPS'),
+ 'nogroup', '/sock_path')
+ self.assertEqual(sample_configs.sample_base_expected_config(
+ frontend=fe, backend=be), rendered_obj)
+
+ def test_render_template_no_monitor_http(self):
+ be = ("backend sample_pool_id_1\n"
+ " mode http\n"
+ " balance roundrobin\n"
+ " cookie SRV insert indirect nocache\n"
+ " option forwardfor\n"
+ " server sample_member_id_1 10.0.0.99:82 weight 13 "
+ "cookie sample_member_id_1\n"
+ " server sample_member_id_2 10.0.0.98:82 weight 13 "
+ "cookie sample_member_id_2\n\n")
+ rendered_obj = jinja_cfg.render_loadbalancer_obj(
+ sample_configs.sample_loadbalancer_tuple(proto='HTTP',
+ monitor=False),
+ 'nogroup', '/sock_path')
+ self.assertEqual(sample_configs.sample_base_expected_config(
+ backend=be), rendered_obj)
+
+ def test_render_template_no_monitor_https(self):
+ fe = ("frontend sample_listener_id_1\n"
+ " option tcplog\n"
+ " maxconn 98\n"
+ " bind 10.0.0.2:80\n"
+ " mode tcp\n"
+ " default_backend sample_pool_id_1\n\n")
+ be = ("backend sample_pool_id_1\n"
+ " mode tcp\n"
+ " balance roundrobin\n"
+ " cookie SRV insert indirect nocache\n"
+ " server sample_member_id_1 10.0.0.99:82 weight 13 "
+ "cookie sample_member_id_1\n"
+ " server sample_member_id_2 10.0.0.98:82 weight 13 "
+ "cookie sample_member_id_2\n\n")
+ rendered_obj = jinja_cfg.render_loadbalancer_obj(
+ sample_configs.sample_loadbalancer_tuple(proto='HTTPS',
+ monitor=False),
+ 'nogroup', '/sock_path')
+ self.assertEqual(sample_configs.sample_base_expected_config(
+ frontend=fe, backend=be), rendered_obj)
+
+ def test_render_template_no_persistence_https(self):
+ fe = ("frontend sample_listener_id_1\n"
+ " option tcplog\n"
+ " maxconn 98\n"
+ " bind 10.0.0.2:80\n"
+ " mode tcp\n"
+ " default_backend sample_pool_id_1\n\n")
+ be = ("backend sample_pool_id_1\n"
+ " mode tcp\n"
+ " balance roundrobin\n"
+ " server sample_member_id_1 10.0.0.99:82 weight 13\n"
+ " server sample_member_id_2 10.0.0.98:82 weight 13\n\n")
+ rendered_obj = jinja_cfg.render_loadbalancer_obj(
+ sample_configs.sample_loadbalancer_tuple(proto='HTTPS',
+ monitor=False,
+ persistence=False),
+ 'nogroup', '/sock_path')
+ self.assertEqual(sample_configs.sample_base_expected_config(
+ frontend=fe, backend=be), rendered_obj)
+
+ def test_render_template_no_persistence_http(self):
+ be = ("backend sample_pool_id_1\n"
+ " mode http\n"
+ " balance roundrobin\n"
+ " option forwardfor\n"
+ " server sample_member_id_1 10.0.0.99:82 weight 13\n"
+ " server sample_member_id_2 10.0.0.98:82 weight 13\n\n")
+ rendered_obj = jinja_cfg.render_loadbalancer_obj(
+ sample_configs.sample_loadbalancer_tuple(proto='HTTP',
+ monitor=False,
+ persistence=False),
+ 'nogroup', '/sock_path')
+ self.assertEqual(sample_configs.sample_base_expected_config(
+ backend=be), rendered_obj)
+
+ def test_render_template_sourceip_persistence(self):
+ be = ("backend sample_pool_id_1\n"
+ " mode http\n"
+ " balance roundrobin\n"
+ " stick-table type ip size 10k\n"
+ " stick on src\n"
+ " timeout check 31\n"
+ " option httpchk GET /index.html\n"
+ " http-check expect rstatus 405|404|500\n"
+ " option forwardfor\n"
+ " server sample_member_id_1 10.0.0.99:82 weight 13 check "
+ "inter 30s fall 3\n"
+ " server sample_member_id_2 10.0.0.98:82 weight 13 check "
+ "inter 30s fall 3\n\n")
+ rendered_obj = jinja_cfg.render_loadbalancer_obj(
+ sample_configs.sample_loadbalancer_tuple(
+ persistence_type='SOURCE_IP'),
+ 'nogroup', '/sock_path')
+ self.assertEqual(
+ sample_configs.sample_base_expected_config(backend=be),
+ rendered_obj)
+
+ def test_render_template_appsession_persistence(self):
+ be = ("backend sample_pool_id_1\n"
+ " mode http\n"
+ " balance roundrobin\n"
+ " appsession APP_COOKIE len 56 timeout 3h\n"
+ " timeout check 31\n"
+ " option httpchk GET /index.html\n"
+ " http-check expect rstatus 405|404|500\n"
+ " option forwardfor\n"
+ " server sample_member_id_1 10.0.0.99:82 weight 13 check "
+ "inter 30s fall 3\n"
+ " server sample_member_id_2 10.0.0.98:82 weight 13 check "
+ "inter 30s fall 3\n\n")
+ rendered_obj = jinja_cfg.render_loadbalancer_obj(
+ sample_configs.sample_loadbalancer_tuple(
+ persistence_type='APP_COOKIE'),
+ 'nogroup', '/sock_path')
+ self.assertEqual(
+ sample_configs.sample_base_expected_config(backend=be),
+ rendered_obj)
+
+ def test_transform_session_persistence(self):
+ in_persistence = sample_configs.sample_session_persistence_tuple()
+ ret = jinja_cfg._transform_session_persistence(in_persistence)
+ self.assertEqual(sample_configs.RET_PERSISTENCE, ret)
+
+ def test_transform_health_monitor(self):
+ in_persistence = sample_configs.sample_health_monitor_tuple()
+ ret = jinja_cfg._transform_health_monitor(in_persistence)
+ self.assertEqual(sample_configs.RET_MONITOR, ret)
+
+ def test_transform_member(self):
+ in_member = sample_configs.sample_member_tuple('sample_member_id_1',
+ '10.0.0.99')
+ ret = jinja_cfg._transform_member(in_member)
+ self.assertEqual(sample_configs.RET_MEMBER_1, ret)
+
+ def test_transform_pool(self):
+ in_pool = sample_configs.sample_pool_tuple()
+ ret = jinja_cfg._transform_pool(in_pool)
+ self.assertEqual(sample_configs.RET_POOL, ret)
+
+ def test_transform_listener(self):
+ in_listener = sample_configs.sample_listener_tuple()
+ ret = jinja_cfg._transform_listener(in_listener)
+ self.assertEqual(sample_configs.RET_LISTENER, ret)
+
+ def test_transform_loadbalancer(self):
+ in_lb = sample_configs.sample_loadbalancer_tuple()
+ ret = jinja_cfg._transform_loadbalancer(in_lb)
+ self.assertEqual(sample_configs.RET_LB, ret) \ No newline at end of file