From 7fe78c59437e7aea502d640651db0a5633c8d1cb Mon Sep 17 00:00:00 2001 From: Michael Johnson Date: Mon, 24 Aug 2020 14:49:06 -0700 Subject: [PATCH] Add proxy v2 protocol support This patch adds support for the proxy protocol v2 on pools. Depends-On: https://review.opendev.org/747296 Change-Id: Ic112c5e71ee9b6433b307fdf27059f217ba4136e Story: 2005611 Task: 30858 --- api-ref/source/parameters.yaml | 2 +- api-ref/source/v2/pool.inc | 3 +- .../feature-matrix-pool.ini | 8 ++ octavia/api/root_controller.py | 5 +- octavia/api/v2/types/pool.py | 9 ++- octavia/common/constants.py | 12 ++- .../haproxy/combined_listeners/jinja_cfg.py | 21 +++-- .../combined_listeners/templates/macros.j2 | 10 ++- ...e6ee84f0abf3_add_proxy_v2_pool_protocol.py | 43 +++++++++++ octavia/tests/common/constants.py | 4 +- .../functional/api/test_root_controller.py | 3 +- .../tests/functional/api/v2/test_l7policy.py | 76 +++++++++++-------- .../tests/functional/api/v2/test_listener.py | 67 +++++++++------- octavia/tests/functional/api/v2/test_pool.py | 32 +++++++- .../combined_listeners/test_jinja_cfg.py | 16 ++-- .../sample_configs/sample_configs_combined.py | 6 +- ...dd-proxy-protocol-v2-90e4f5bf76138c69.yaml | 4 + 17 files changed, 231 insertions(+), 90 deletions(-) create mode 100644 octavia/db/migration/alembic_migrations/versions/e6ee84f0abf3_add_proxy_v2_pool_protocol.py create mode 100644 releasenotes/notes/Add-proxy-protocol-v2-90e4f5bf76138c69.yaml diff --git a/api-ref/source/parameters.yaml b/api-ref/source/parameters.yaml index 0b047833d3..7aea6d0aff 100644 --- a/api-ref/source/parameters.yaml +++ b/api-ref/source/parameters.yaml @@ -1236,7 +1236,7 @@ protocol: protocol-pools: description: | The protocol for the resource. One of ``HTTP``, ``HTTPS``, ``PROXY``, - ``TCP``, or ``UDP``. + ``PROXYV2``, ``TCP``, or ``UDP``. in: body required: true type: string diff --git a/api-ref/source/v2/pool.inc b/api-ref/source/v2/pool.inc index fa60721a2a..96923ca9c6 100644 --- a/api-ref/source/v2/pool.inc +++ b/api-ref/source/v2/pool.inc @@ -102,7 +102,8 @@ is ready for further configuration. At a minimum, you must specify these pool attributes: - ``protocol`` The protocol for which this pool and its members - listen. A valid value is ``HTTP``, ``HTTPS``, ``PROXY``, ``TCP``, or ``UDP``. + listen. A valid value is ``HTTP``, ``HTTPS``, ``PROXY``, ``PROXYV2``, + ``TCP``, or ``UDP``. - ``lb_algorithm`` The load-balancer algorithm, such as ``ROUND_ROBIN``, ``LEAST_CONNECTIONS``, ``SOURCE_IP`` and ``SOURCE_IP_PORT``, diff --git a/doc/source/user/feature-classification/feature-matrix-pool.ini b/doc/source/user/feature-classification/feature-matrix-pool.ini index 9725297a56..69e19c6646 100644 --- a/doc/source/user/feature-classification/feature-matrix-pool.ini +++ b/doc/source/user/feature-classification/feature-matrix-pool.ini @@ -114,6 +114,14 @@ cli=openstack loadbalancer pool create --protocol PROXY --listener driver.amphora=complete driver.ovn=missing +[operation.protocol.PROXYV2] +title=protocol - PROXYV2 +status=optional +notes=PROXY protocol version 2 support for the pool. +cli=openstack loadbalancer pool create --protocol PROXYV2 --listener +driver.amphora=complete +driver.ovn=missing + [operation.protocol.TCP] title=protocol - TCP status=optional diff --git a/octavia/api/root_controller.py b/octavia/api/root_controller.py index d8333b6a84..82524589a4 100644 --- a/octavia/api/root_controller.py +++ b/octavia/api/root_controller.py @@ -128,6 +128,9 @@ class RootController(object): self._add_a_version(versions, 'v2.20', 'v2', 'SUPPORTED', '2020-08-02T00:00:00Z', host_url) # Amphora delete - self._add_a_version(versions, 'v2.21', 'v2', 'CURRENT', + self._add_a_version(versions, 'v2.21', 'v2', 'SUPPORTED', '2020-09-03T00:00:00Z', host_url) + # Add PROXYV2 pool protocol + self._add_a_version(versions, 'v2.22', 'v2', 'CURRENT', + '2020-09-04T00:00:00Z', host_url) return {'versions': versions} diff --git a/octavia/api/v2/types/pool.py b/octavia/api/v2/types/pool.py index b32aba6e48..1ad97616e1 100644 --- a/octavia/api/v2/types/pool.py +++ b/octavia/api/v2/types/pool.py @@ -12,6 +12,7 @@ # License for the specific language governing permissions and limitations # under the License. +from octavia_lib.common import constants as lib_constants from wsme import types as wtypes from octavia.api.common import types @@ -146,8 +147,9 @@ class PoolPOST(BasePoolType): admin_state_up = wtypes.wsattr(bool, default=True) listener_id = wtypes.wsattr(wtypes.UuidType()) loadbalancer_id = wtypes.wsattr(wtypes.UuidType()) - protocol = wtypes.wsattr(wtypes.Enum(str, *constants.SUPPORTED_PROTOCOLS), - mandatory=True) + protocol = wtypes.wsattr( + wtypes.Enum(str, *lib_constants.POOL_SUPPORTED_PROTOCOLS), + mandatory=True) lb_algorithm = wtypes.wsattr( wtypes.Enum(str, *constants.SUPPORTED_LB_ALGORITHMS), mandatory=True) @@ -198,7 +200,8 @@ class PoolSingleCreate(BasePoolType): name = wtypes.wsattr(wtypes.StringType(max_length=255)) description = wtypes.wsattr(wtypes.StringType(max_length=255)) admin_state_up = wtypes.wsattr(bool, default=True) - protocol = wtypes.wsattr(wtypes.Enum(str, *constants.SUPPORTED_PROTOCOLS)) + protocol = wtypes.wsattr( + wtypes.Enum(str, *lib_constants.POOL_SUPPORTED_PROTOCOLS)) lb_algorithm = wtypes.wsattr( wtypes.Enum(str, *constants.SUPPORTED_LB_ALGORITHMS)) session_persistence = wtypes.wsattr(SessionPersistencePOST) diff --git a/octavia/common/constants.py b/octavia/common/constants.py index 1ed4a019ec..826c8ed261 100644 --- a/octavia/common/constants.py +++ b/octavia/common/constants.py @@ -207,10 +207,13 @@ UPDATE_HEALTH = 'UPDATE_HEALTH' VALID_LISTENER_POOL_PROTOCOL_MAP = { PROTOCOL_TCP: [PROTOCOL_HTTP, PROTOCOL_HTTPS, - PROTOCOL_PROXY, PROTOCOL_TCP], - PROTOCOL_HTTP: [PROTOCOL_HTTP, PROTOCOL_PROXY], - PROTOCOL_HTTPS: [PROTOCOL_HTTPS, PROTOCOL_PROXY, PROTOCOL_TCP], - PROTOCOL_TERMINATED_HTTPS: [PROTOCOL_HTTP, PROTOCOL_PROXY], + PROTOCOL_PROXY, lib_consts.PROTOCOL_PROXYV2, PROTOCOL_TCP], + PROTOCOL_HTTP: [PROTOCOL_HTTP, PROTOCOL_PROXY, + lib_consts.PROTOCOL_PROXYV2], + PROTOCOL_HTTPS: [PROTOCOL_HTTPS, PROTOCOL_PROXY, + lib_consts.PROTOCOL_PROXYV2, PROTOCOL_TCP], + PROTOCOL_TERMINATED_HTTPS: [PROTOCOL_HTTP, PROTOCOL_PROXY, + lib_consts.PROTOCOL_PROXYV2], PROTOCOL_UDP: [PROTOCOL_UDP]} # API Integer Ranges @@ -810,6 +813,7 @@ L4_PROTOCOL_MAP = { PROTOCOL_HTTPS: PROTOCOL_TCP, PROTOCOL_TERMINATED_HTTPS: PROTOCOL_TCP, PROTOCOL_PROXY: PROTOCOL_TCP, + lib_consts.PROTOCOL_PROXYV2: PROTOCOL_TCP, PROTOCOL_UDP: PROTOCOL_UDP, } diff --git a/octavia/common/jinja/haproxy/combined_listeners/jinja_cfg.py b/octavia/common/jinja/haproxy/combined_listeners/jinja_cfg.py index 942025203e..6f3da53f85 100644 --- a/octavia/common/jinja/haproxy/combined_listeners/jinja_cfg.py +++ b/octavia/common/jinja/haproxy/combined_listeners/jinja_cfg.py @@ -284,7 +284,9 @@ class JinjaTemplater(object): os.path.join(self.base_crt_dir, loadbalancer.id, tls_certs[listener.client_crl_container_id])) + tls_enabled = False if listener.protocol == constants.PROTOCOL_TERMINATED_HTTPS: + tls_enabled = True if listener.tls_ciphers is not None: ret_value['tls_ciphers'] = listener.tls_ciphers if listener.tls_versions is not None: @@ -300,7 +302,7 @@ class JinjaTemplater(object): if tls_certs is not None and tls_certs.get(pool.id): kwargs = {'pool_tls_certs': tls_certs.get(pool.id)} pools.append(self._transform_pool( - pool, feature_compatibility, **kwargs)) + pool, feature_compatibility, tls_enabled, **kwargs)) ret_value['pools'] = pools policy_gen = (policy for policy in listener.l7policies if policy.provisioning_status != constants.PENDING_DELETE) @@ -311,21 +313,27 @@ class JinjaTemplater(object): break l7policies = [self._transform_l7policy( - x, feature_compatibility, tls_certs) + x, feature_compatibility, tls_enabled, tls_certs) for x in policy_gen] ret_value['l7policies'] = l7policies return ret_value def _transform_pool(self, pool, feature_compatibility, - pool_tls_certs=None): + listener_tls_enabled, pool_tls_certs=None): """Transforms a pool into an object that will be processed by the templating system """ + proxy_protocol_version = None + if pool.protocol == constants.PROTOCOL_PROXY: + proxy_protocol_version = 1 + if pool.protocol == lib_consts.PROTOCOL_PROXYV2: + proxy_protocol_version = 2 ret_value = { 'id': pool.id, 'protocol': PROTOCOL_MAP[pool.protocol], - 'proxy_protocol': pool.protocol == constants.PROTOCOL_PROXY, + 'proxy_protocol': proxy_protocol_version, + 'listener_tls_enabled': listener_tls_enabled, 'lb_algorithm': BALANCE_MAP.get(pool.lb_algorithm, 'roundrobin'), 'members': [], 'health_monitor': '', @@ -425,7 +433,7 @@ class JinjaTemplater(object): } def _transform_l7policy(self, l7policy, feature_compatibility, - tls_certs=None): + listener_tls_enabled, tls_certs=None): """Transforms an L7 policy into an object that will be processed by the templating system @@ -446,7 +454,8 @@ class JinjaTemplater(object): kwargs = {'pool_tls_certs': tls_certs.get(l7policy.redirect_pool.id)} ret_value['redirect_pool'] = self._transform_pool( - l7policy.redirect_pool, feature_compatibility, **kwargs) + l7policy.redirect_pool, feature_compatibility, + listener_tls_enabled, **kwargs) else: ret_value['redirect_pool'] = None if (l7policy.action in [constants.L7POLICY_ACTION_REDIRECT_TO_URL, diff --git a/octavia/common/jinja/haproxy/combined_listeners/templates/macros.j2 b/octavia/common/jinja/haproxy/combined_listeners/templates/macros.j2 index 41077267ec..106687b07c 100644 --- a/octavia/common/jinja/haproxy/combined_listeners/templates/macros.j2 +++ b/octavia/common/jinja/haproxy/combined_listeners/templates/macros.j2 @@ -217,8 +217,14 @@ frontend {{ listener.id }} {% else %} {% set persistence_opt = "" %} {% endif %} - {% if pool.proxy_protocol %} + {% if pool.proxy_protocol == 1 %} {% set proxy_protocol_opt = " send-proxy" %} + {% elif pool.proxy_protocol == 2 %} + {% if pool.listener_tls_enabled %} + {% set proxy_protocol_opt = " send-proxy-v2-ssl-cn" %} + {% else %} + {% set proxy_protocol_opt = " send-proxy-v2" %} + {% endif %} {% else %} {% set proxy_protocol_opt = "" %} {% endif %} @@ -288,7 +294,7 @@ frontend {{ listener.id }} {% macro backend_macro(constants, lib_consts, listener, pool, loadbalancer) %} backend {{ pool.id }}:{{ listener.id }} - {% if pool.protocol.lower() == constants.PROTOCOL_PROXY.lower() %} + {% if pool.proxy_protocol is not none %} mode {{ listener.protocol_mode }} {% else %} mode {{ pool.protocol }} diff --git a/octavia/db/migration/alembic_migrations/versions/e6ee84f0abf3_add_proxy_v2_pool_protocol.py b/octavia/db/migration/alembic_migrations/versions/e6ee84f0abf3_add_proxy_v2_pool_protocol.py new file mode 100644 index 0000000000..663c1e2cfd --- /dev/null +++ b/octavia/db/migration/alembic_migrations/versions/e6ee84f0abf3_add_proxy_v2_pool_protocol.py @@ -0,0 +1,43 @@ +# Copyright 2020 Red Hat, Inc. 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. +"""Add PROXY v2 pool protocol + +Revision ID: e6ee84f0abf3 +Revises: 2ab994dd3ec2 +Create Date: 2020-08-24 11:12:46.745185 + +""" +from alembic import op +import sqlalchemy as sa +from sqlalchemy import sql + + +# revision identifiers, used by Alembic. +revision = 'e6ee84f0abf3' +down_revision = '2ab994dd3ec2' + + +def upgrade(): + insert_table = sql.table( + u'protocol', + sql.column(u'name', sa.String), + sql.column(u'description', sa.String) + ) + + op.bulk_insert( + insert_table, + [ + {'name': 'PROXYV2'} + ] + ) diff --git a/octavia/tests/common/constants.py b/octavia/tests/common/constants.py index 03ac0c9e6c..e07e7db4d0 100644 --- a/octavia/tests/common/constants.py +++ b/octavia/tests/common/constants.py @@ -11,6 +11,7 @@ # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. +from octavia_lib.common import constants as lib_constants from octavia.common import constants @@ -280,4 +281,5 @@ INVALID_LISTENER_POOL_PROTOCOL_MAP = { constants.PROTOCOL_HTTP, constants.PROTOCOL_HTTPS, constants.PROTOCOL_TERMINATED_HTTPS, - constants.PROTOCOL_PROXY]} + constants.PROTOCOL_PROXY, + lib_constants.PROTOCOL_PROXYV2]} diff --git a/octavia/tests/functional/api/test_root_controller.py b/octavia/tests/functional/api/test_root_controller.py index cb1c42867e..fe70b8e759 100644 --- a/octavia/tests/functional/api/test_root_controller.py +++ b/octavia/tests/functional/api/test_root_controller.py @@ -45,7 +45,7 @@ class TestRootController(base_db_test.OctaviaDBTestBase): def test_api_versions(self): versions = self._get_versions_with_config() version_ids = tuple(v.get('id') for v in versions) - self.assertEqual(22, len(version_ids)) + self.assertEqual(23, len(version_ids)) self.assertIn('v2.0', version_ids) self.assertIn('v2.1', version_ids) self.assertIn('v2.2', version_ids) @@ -68,6 +68,7 @@ class TestRootController(base_db_test.OctaviaDBTestBase): self.assertIn('v2.19', version_ids) self.assertIn('v2.20', version_ids) self.assertIn('v2.21', version_ids) + self.assertIn('v2.22', version_ids) # Each version should have a 'self' 'href' to the API version URL # [{u'rel': u'self', u'href': u'http://localhost/v2'}] diff --git a/octavia/tests/functional/api/v2/test_l7policy.py b/octavia/tests/functional/api/v2/test_l7policy.py index 37b5dab3b6..544f736e19 100644 --- a/octavia/tests/functional/api/v2/test_l7policy.py +++ b/octavia/tests/functional/api/v2/test_l7policy.py @@ -1303,21 +1303,27 @@ class TestL7Policy(base.BaseAPITest): self.set_object_status(self.lb_repo, self.lb_id) port = port + 1 for pool_proto in invalid_map[listener_proto]: - pool = self.create_pool( - self.lb_id, pool_proto, - constants.LB_ALGORITHM_ROUND_ROBIN).get('pool') - self.set_object_status(self.lb_repo, self.lb_id) + if pool_proto == constants.PROTOCOL_TERMINATED_HTTPS: + pool = self.create_pool( + self.lb_id, pool_proto, + constants.LB_ALGORITHM_ROUND_ROBIN, status=400) + self.assertIn("Invalid input", pool['faultstring']) + else: + pool = self.create_pool( + self.lb_id, pool_proto, + constants.LB_ALGORITHM_ROUND_ROBIN).get('pool') + self.set_object_status(self.lb_repo, self.lb_id) - l7policy['listener_id'] = listener.get('id') - l7policy['redirect_pool_id'] = pool.get('id') - expect_error_msg = ("Validation failure: The pool protocol " - "'%s' is invalid while the listener " - "protocol is '%s'.") % (pool_proto, - listener_proto) - res = self.post(self.L7POLICIES_PATH, - self._build_body(l7policy), status=400) - self.assertEqual(expect_error_msg, res.json['faultstring']) - self.assert_correct_status(lb_id=self.lb_id) + l7policy['listener_id'] = listener.get('id') + l7policy['redirect_pool_id'] = pool.get('id') + expect_error_msg = ( + "Validation failure: The pool protocol '%s' is " + "invalid while the listener protocol is '%s'.") % ( + pool_proto, listener_proto) + res = self.post(self.L7POLICIES_PATH, + self._build_body(l7policy), status=400) + self.assertEqual(expect_error_msg, res.json['faultstring']) + self.assert_correct_status(lb_id=self.lb_id) @mock.patch('octavia.common.tls_utils.cert_parser.load_certificates_data') def test_listener_pool_protocol_map_put(self, mock_cert_data): @@ -1361,21 +1367,27 @@ class TestL7Policy(base.BaseAPITest): self.set_object_status(self.lb_repo, self.lb_id) port = port + 1 for pool_proto in invalid_map[listener_proto]: - pool = self.create_pool( - self.lb_id, pool_proto, - constants.LB_ALGORITHM_ROUND_ROBIN).get('pool') - self.set_object_status(self.lb_repo, self.lb_id) - l7policy = self.create_l7policy( - listener.get('id'), - constants.L7POLICY_ACTION_REJECT).get(self.root_tag) - self.set_object_status(self.lb_repo, self.lb_id) - new_l7policy['redirect_pool_id'] = pool.get('id') - expect_error_msg = ("Validation failure: The pool protocol " - "'%s' is invalid while the listener " - "protocol is '%s'.") % (pool_proto, - listener_proto) - res = self.put(self.L7POLICY_PATH.format( - l7policy_id=l7policy.get('id')), - self._build_body(new_l7policy), status=400) - self.assertEqual(expect_error_msg, res.json['faultstring']) - self.assert_correct_status(lb_id=self.lb_id) + if pool_proto == constants.PROTOCOL_TERMINATED_HTTPS: + pool = self.create_pool( + self.lb_id, pool_proto, + constants.LB_ALGORITHM_ROUND_ROBIN, status=400) + self.assertIn("Invalid input", pool['faultstring']) + else: + pool = self.create_pool( + self.lb_id, pool_proto, + constants.LB_ALGORITHM_ROUND_ROBIN).get('pool') + self.set_object_status(self.lb_repo, self.lb_id) + l7policy = self.create_l7policy( + listener.get('id'), + constants.L7POLICY_ACTION_REJECT).get(self.root_tag) + self.set_object_status(self.lb_repo, self.lb_id) + new_l7policy['redirect_pool_id'] = pool.get('id') + expect_error_msg = ( + "Validation failure: The pool protocol '%s' is " + "invalid while the listener protocol is '%s'.") % ( + pool_proto, listener_proto) + res = self.put(self.L7POLICY_PATH.format( + l7policy_id=l7policy.get('id')), + self._build_body(new_l7policy), status=400) + self.assertEqual(expect_error_msg, res.json['faultstring']) + self.assert_correct_status(lb_id=self.lb_id) diff --git a/octavia/tests/functional/api/v2/test_listener.py b/octavia/tests/functional/api/v2/test_listener.py index f8cc0e9d52..d3a7d67db9 100644 --- a/octavia/tests/functional/api/v2/test_listener.py +++ b/octavia/tests/functional/api/v2/test_listener.py @@ -2849,23 +2849,29 @@ class TestListener(base.BaseAPITest): for listener_proto in invalid_map: for pool_proto in invalid_map[listener_proto]: port = port + 1 - pool = self.create_pool( - self.lb_id, pool_proto, - constants.LB_ALGORITHM_ROUND_ROBIN).get('pool') - self.set_object_status(self.lb_repo, self.lb_id) - expect_error_msg = ("Validation failure: The pool protocol " - "'%s' is invalid while the listener " - "protocol is '%s'.") % (pool_proto, - listener_proto) - listener = {'protocol': listener_proto, - 'protocol_port': port, - 'loadbalancer_id': self.lb_id, - 'default_pool_id': pool.get('id')} - body = self._build_body(listener) - res = self.post(self.LISTENERS_PATH, body, - status=400, expect_errors=True) - self.assertEqual(expect_error_msg, res.json['faultstring']) - self.assert_correct_status(lb_id=self.lb_id) + if pool_proto == constants.PROTOCOL_TERMINATED_HTTPS: + pool = self.create_pool( + self.lb_id, pool_proto, + constants.LB_ALGORITHM_ROUND_ROBIN, status=400) + self.assertIn("Invalid input", pool['faultstring']) + else: + pool = self.create_pool( + self.lb_id, pool_proto, + constants.LB_ALGORITHM_ROUND_ROBIN).get('pool') + self.set_object_status(self.lb_repo, self.lb_id) + expect_error_msg = ( + "Validation failure: The pool protocol '%s' is " + "invalid while the listener protocol is '%s'.") % ( + pool_proto, listener_proto) + listener = {'protocol': listener_proto, + 'protocol_port': port, + 'loadbalancer_id': self.lb_id, + 'default_pool_id': pool.get('id')} + body = self._build_body(listener) + res = self.post(self.LISTENERS_PATH, body, + status=400, expect_errors=True) + self.assertEqual(expect_error_msg, res.json['faultstring']) + self.assert_correct_status(lb_id=self.lb_id) @mock.patch('octavia.common.tls_utils.cert_parser.load_certificates_data') def test_listener_pool_protocol_map_put(self, mock_cert_data): @@ -2907,13 +2913,20 @@ class TestListener(base.BaseAPITest): "'%s' is invalid while the listener " "protocol is '%s'.") % (pool_proto, listener_proto) - pool = self.create_pool( - self.lb_id, pool_proto, - constants.LB_ALGORITHM_ROUND_ROBIN).get('pool') - self.set_object_status(self.lb_repo, self.lb_id) - new_listener = {'default_pool_id': pool.get('id')} - res = self.put( - self.LISTENER_PATH.format(listener_id=listener.get('id')), - self._build_body(new_listener), status=400) - self.assertEqual(expect_error_msg, res.json['faultstring']) - self.assert_correct_status(lb_id=self.lb_id) + if pool_proto == constants.PROTOCOL_TERMINATED_HTTPS: + pool = self.create_pool( + self.lb_id, pool_proto, + constants.LB_ALGORITHM_ROUND_ROBIN, status=400) + self.assertIn("Invalid input", pool['faultstring']) + else: + pool = self.create_pool( + self.lb_id, pool_proto, + constants.LB_ALGORITHM_ROUND_ROBIN).get('pool') + self.set_object_status(self.lb_repo, self.lb_id) + new_listener = {'default_pool_id': pool.get('id')} + res = self.put( + self.LISTENER_PATH.format( + listener_id=listener.get('id')), + self._build_body(new_listener), status=400) + self.assertEqual(expect_error_msg, res.json['faultstring']) + self.assert_correct_status(lb_id=self.lb_id) diff --git a/octavia/tests/functional/api/v2/test_pool.py b/octavia/tests/functional/api/v2/test_pool.py index e0d1161cc4..b2c0b7b062 100644 --- a/octavia/tests/functional/api/v2/test_pool.py +++ b/octavia/tests/functional/api/v2/test_pool.py @@ -14,6 +14,7 @@ from unittest import mock +from octavia_lib.common import constants as lib_constants from oslo_config import cfg from oslo_config import fixture as oslo_fixture from oslo_utils import uuidutils @@ -765,6 +766,30 @@ class TestPool(base.BaseAPITest): lb_id=self.lb_id, listener_id=self.listener_id, pool_id=api_pool.get('id')) + def test_create_with_proxy_v2_protocol(self): + api_pool = self.create_pool( + self.lb_id, + lib_constants.PROTOCOL_PROXYV2, + constants.LB_ALGORITHM_ROUND_ROBIN, + listener_id=self.listener_id).get(self.root_tag) + self.assert_correct_status( + lb_id=self.lb_id, listener_id=self.listener_id, + pool_id=api_pool.get('id'), + lb_prov_status=constants.PENDING_UPDATE, + listener_prov_status=constants.PENDING_UPDATE, + pool_prov_status=constants.PENDING_CREATE, + pool_op_status=constants.OFFLINE) + self.set_lb_status(self.lb_id) + self.assertEqual(lib_constants.PROTOCOL_PROXYV2, + api_pool.get('protocol')) + self.assertEqual(constants.LB_ALGORITHM_ROUND_ROBIN, + api_pool.get('lb_algorithm')) + self.assertIsNotNone(api_pool.get('created_at')) + self.assertIsNone(api_pool.get('updated_at')) + self.assert_correct_status( + lb_id=self.lb_id, listener_id=self.listener_id, + pool_id=api_pool.get('id')) + def test_create_sans_listener(self): api_pool = self.create_pool( self.lb_id, @@ -2382,5 +2407,10 @@ class TestPool(base.BaseAPITest): lb_pool['listener_id'] = listener.get('id') res = self.post(self.POOLS_PATH, self._build_body(lb_pool), status=400, expect_errors=True) - self.assertEqual(expect_error_msg, res.json['faultstring']) + if pool_proto == constants.PROTOCOL_TERMINATED_HTTPS: + self.assertIn('Invalid input', + res.json['faultstring']) + else: + self.assertEqual(expect_error_msg, + res.json['faultstring']) self.assert_correct_status(lb_id=self.lb_id) diff --git a/octavia/tests/unit/common/jinja/haproxy/combined_listeners/test_jinja_cfg.py b/octavia/tests/unit/common/jinja/haproxy/combined_listeners/test_jinja_cfg.py index e9fc6d3c3f..ccbf1b24f3 100644 --- a/octavia/tests/unit/common/jinja/haproxy/combined_listeners/test_jinja_cfg.py +++ b/octavia/tests/unit/common/jinja/haproxy/combined_listeners/test_jinja_cfg.py @@ -1288,18 +1288,18 @@ class TestHaproxyCfg(base.TestCase): def test_transform_pool(self): in_pool = sample_configs_combined.sample_pool_tuple() - ret = self.jinja_cfg._transform_pool(in_pool, {}) + ret = self.jinja_cfg._transform_pool(in_pool, {}, False) self.assertEqual(sample_configs_combined.RET_POOL_1, ret) def test_transform_pool_2(self): in_pool = sample_configs_combined.sample_pool_tuple(sample_pool=2) - ret = self.jinja_cfg._transform_pool(in_pool, {}) + ret = self.jinja_cfg._transform_pool(in_pool, {}, False) self.assertEqual(sample_configs_combined.RET_POOL_2, ret) def test_transform_pool_http_reuse(self): in_pool = sample_configs_combined.sample_pool_tuple(sample_pool=2) ret = self.jinja_cfg._transform_pool( - in_pool, {constants.HTTP_REUSE: True}) + in_pool, {constants.HTTP_REUSE: True}, False) expected_config = copy.copy(sample_configs_combined.RET_POOL_2) expected_config[constants.HTTP_REUSE] = True self.assertEqual(expected_config, ret) @@ -1309,7 +1309,7 @@ class TestHaproxyCfg(base.TestCase): cert_path = os.path.join(self.jinja_cfg.base_crt_dir, 'test_listener_id', 'pool_cert.pem') ret = self.jinja_cfg._transform_pool( - in_pool, {}, pool_tls_certs={'client_cert': cert_path}) + in_pool, {}, False, pool_tls_certs={'client_cert': cert_path}) expected_config = copy.copy(sample_configs_combined.RET_POOL_1) expected_config['client_cert'] = cert_path self.assertEqual(expected_config, ret) @@ -1383,25 +1383,25 @@ class TestHaproxyCfg(base.TestCase): def test_transform_l7policy(self): in_l7policy = sample_configs_combined.sample_l7policy_tuple( 'sample_l7policy_id_1') - ret = self.jinja_cfg._transform_l7policy(in_l7policy, {}) + ret = self.jinja_cfg._transform_l7policy(in_l7policy, {}, False) self.assertEqual(sample_configs_combined.RET_L7POLICY_1, ret) def test_transform_l7policy_2_8(self): in_l7policy = sample_configs_combined.sample_l7policy_tuple( 'sample_l7policy_id_2', sample_policy=2) - ret = self.jinja_cfg._transform_l7policy(in_l7policy, {}) + ret = self.jinja_cfg._transform_l7policy(in_l7policy, {}, False) self.assertEqual(sample_configs_combined.RET_L7POLICY_2, ret) # test invalid action without redirect_http_code in_l7policy = sample_configs_combined.sample_l7policy_tuple( 'sample_l7policy_id_8', sample_policy=2, redirect_http_code=None) - ret = self.jinja_cfg._transform_l7policy(in_l7policy, {}) + ret = self.jinja_cfg._transform_l7policy(in_l7policy, {}, False) self.assertEqual(sample_configs_combined.RET_L7POLICY_8, ret) def test_transform_l7policy_disabled_rule(self): in_l7policy = sample_configs_combined.sample_l7policy_tuple( 'sample_l7policy_id_6', sample_policy=6) - ret = self.jinja_cfg._transform_l7policy(in_l7policy, {}) + ret = self.jinja_cfg._transform_l7policy(in_l7policy, {}, False) self.assertEqual(sample_configs_combined.RET_L7POLICY_6, ret) def test_escape_haproxy_config_string(self): diff --git a/octavia/tests/unit/common/sample_configs/sample_configs_combined.py b/octavia/tests/unit/common/sample_configs/sample_configs_combined.py index ef41ad6d79..ca0c111f78 100644 --- a/octavia/tests/unit/common/sample_configs/sample_configs_combined.py +++ b/octavia/tests/unit/common/sample_configs/sample_configs_combined.py @@ -117,8 +117,9 @@ RET_MEMBER_3 = { RET_POOL_1 = { 'id': 'sample_pool_id_1', 'protocol': 'http', - 'proxy_protocol': False, + 'proxy_protocol': None, 'lb_algorithm': 'roundrobin', + 'listener_tls_enabled': False, 'members': [RET_MEMBER_1, RET_MEMBER_2], 'health_monitor': RET_MONITOR_1, 'session_persistence': RET_PERSISTENCE, @@ -134,8 +135,9 @@ RET_POOL_1 = { RET_POOL_2 = { 'id': 'sample_pool_id_2', 'protocol': 'http', - 'proxy_protocol': False, + 'proxy_protocol': None, 'lb_algorithm': 'roundrobin', + 'listener_tls_enabled': False, 'members': [RET_MEMBER_3], 'health_monitor': RET_MONITOR_2, 'session_persistence': RET_PERSISTENCE, diff --git a/releasenotes/notes/Add-proxy-protocol-v2-90e4f5bf76138c69.yaml b/releasenotes/notes/Add-proxy-protocol-v2-90e4f5bf76138c69.yaml new file mode 100644 index 0000000000..985a6d6a6d --- /dev/null +++ b/releasenotes/notes/Add-proxy-protocol-v2-90e4f5bf76138c69.yaml @@ -0,0 +1,4 @@ +--- +features: + - | + Added support for proxy protocol version 2.