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
This commit is contained in:
Michael Johnson 2020-08-24 14:49:06 -07:00
parent 46de66b240
commit 7fe78c5943
17 changed files with 231 additions and 90 deletions

View File

@ -1236,7 +1236,7 @@ protocol:
protocol-pools: protocol-pools:
description: | description: |
The protocol for the resource. One of ``HTTP``, ``HTTPS``, ``PROXY``, The protocol for the resource. One of ``HTTP``, ``HTTPS``, ``PROXY``,
``TCP``, or ``UDP``. ``PROXYV2``, ``TCP``, or ``UDP``.
in: body in: body
required: true required: true
type: string type: string

View File

@ -102,7 +102,8 @@ is ready for further configuration.
At a minimum, you must specify these pool attributes: At a minimum, you must specify these pool attributes:
- ``protocol`` The protocol for which this pool and its members - ``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 - ``lb_algorithm`` The load-balancer algorithm, such as
``ROUND_ROBIN``, ``LEAST_CONNECTIONS``, ``SOURCE_IP`` and ``SOURCE_IP_PORT``, ``ROUND_ROBIN``, ``LEAST_CONNECTIONS``, ``SOURCE_IP`` and ``SOURCE_IP_PORT``,

View File

@ -114,6 +114,14 @@ cli=openstack loadbalancer pool create --protocol PROXY --listener <listener>
driver.amphora=complete driver.amphora=complete
driver.ovn=missing 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 <listener>
driver.amphora=complete
driver.ovn=missing
[operation.protocol.TCP] [operation.protocol.TCP]
title=protocol - TCP title=protocol - TCP
status=optional status=optional

View File

@ -128,6 +128,9 @@ class RootController(object):
self._add_a_version(versions, 'v2.20', 'v2', 'SUPPORTED', self._add_a_version(versions, 'v2.20', 'v2', 'SUPPORTED',
'2020-08-02T00:00:00Z', host_url) '2020-08-02T00:00:00Z', host_url)
# Amphora delete # 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) '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} return {'versions': versions}

View File

@ -12,6 +12,7 @@
# License for the specific language governing permissions and limitations # License for the specific language governing permissions and limitations
# under the License. # under the License.
from octavia_lib.common import constants as lib_constants
from wsme import types as wtypes from wsme import types as wtypes
from octavia.api.common import types from octavia.api.common import types
@ -146,8 +147,9 @@ class PoolPOST(BasePoolType):
admin_state_up = wtypes.wsattr(bool, default=True) admin_state_up = wtypes.wsattr(bool, default=True)
listener_id = wtypes.wsattr(wtypes.UuidType()) listener_id = wtypes.wsattr(wtypes.UuidType())
loadbalancer_id = wtypes.wsattr(wtypes.UuidType()) loadbalancer_id = wtypes.wsattr(wtypes.UuidType())
protocol = wtypes.wsattr(wtypes.Enum(str, *constants.SUPPORTED_PROTOCOLS), protocol = wtypes.wsattr(
mandatory=True) wtypes.Enum(str, *lib_constants.POOL_SUPPORTED_PROTOCOLS),
mandatory=True)
lb_algorithm = wtypes.wsattr( lb_algorithm = wtypes.wsattr(
wtypes.Enum(str, *constants.SUPPORTED_LB_ALGORITHMS), wtypes.Enum(str, *constants.SUPPORTED_LB_ALGORITHMS),
mandatory=True) mandatory=True)
@ -198,7 +200,8 @@ class PoolSingleCreate(BasePoolType):
name = wtypes.wsattr(wtypes.StringType(max_length=255)) name = wtypes.wsattr(wtypes.StringType(max_length=255))
description = wtypes.wsattr(wtypes.StringType(max_length=255)) description = wtypes.wsattr(wtypes.StringType(max_length=255))
admin_state_up = wtypes.wsattr(bool, default=True) 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( lb_algorithm = wtypes.wsattr(
wtypes.Enum(str, *constants.SUPPORTED_LB_ALGORITHMS)) wtypes.Enum(str, *constants.SUPPORTED_LB_ALGORITHMS))
session_persistence = wtypes.wsattr(SessionPersistencePOST) session_persistence = wtypes.wsattr(SessionPersistencePOST)

View File

@ -207,10 +207,13 @@ UPDATE_HEALTH = 'UPDATE_HEALTH'
VALID_LISTENER_POOL_PROTOCOL_MAP = { VALID_LISTENER_POOL_PROTOCOL_MAP = {
PROTOCOL_TCP: [PROTOCOL_HTTP, PROTOCOL_HTTPS, PROTOCOL_TCP: [PROTOCOL_HTTP, PROTOCOL_HTTPS,
PROTOCOL_PROXY, PROTOCOL_TCP], PROTOCOL_PROXY, lib_consts.PROTOCOL_PROXYV2, PROTOCOL_TCP],
PROTOCOL_HTTP: [PROTOCOL_HTTP, PROTOCOL_PROXY], PROTOCOL_HTTP: [PROTOCOL_HTTP, PROTOCOL_PROXY,
PROTOCOL_HTTPS: [PROTOCOL_HTTPS, PROTOCOL_PROXY, PROTOCOL_TCP], lib_consts.PROTOCOL_PROXYV2],
PROTOCOL_TERMINATED_HTTPS: [PROTOCOL_HTTP, PROTOCOL_PROXY], 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]} PROTOCOL_UDP: [PROTOCOL_UDP]}
# API Integer Ranges # API Integer Ranges
@ -810,6 +813,7 @@ L4_PROTOCOL_MAP = {
PROTOCOL_HTTPS: PROTOCOL_TCP, PROTOCOL_HTTPS: PROTOCOL_TCP,
PROTOCOL_TERMINATED_HTTPS: PROTOCOL_TCP, PROTOCOL_TERMINATED_HTTPS: PROTOCOL_TCP,
PROTOCOL_PROXY: PROTOCOL_TCP, PROTOCOL_PROXY: PROTOCOL_TCP,
lib_consts.PROTOCOL_PROXYV2: PROTOCOL_TCP,
PROTOCOL_UDP: PROTOCOL_UDP, PROTOCOL_UDP: PROTOCOL_UDP,
} }

View File

@ -284,7 +284,9 @@ class JinjaTemplater(object):
os.path.join(self.base_crt_dir, loadbalancer.id, os.path.join(self.base_crt_dir, loadbalancer.id,
tls_certs[listener.client_crl_container_id])) tls_certs[listener.client_crl_container_id]))
tls_enabled = False
if listener.protocol == constants.PROTOCOL_TERMINATED_HTTPS: if listener.protocol == constants.PROTOCOL_TERMINATED_HTTPS:
tls_enabled = True
if listener.tls_ciphers is not None: if listener.tls_ciphers is not None:
ret_value['tls_ciphers'] = listener.tls_ciphers ret_value['tls_ciphers'] = listener.tls_ciphers
if listener.tls_versions is not None: 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): if tls_certs is not None and tls_certs.get(pool.id):
kwargs = {'pool_tls_certs': tls_certs.get(pool.id)} kwargs = {'pool_tls_certs': tls_certs.get(pool.id)}
pools.append(self._transform_pool( pools.append(self._transform_pool(
pool, feature_compatibility, **kwargs)) pool, feature_compatibility, tls_enabled, **kwargs))
ret_value['pools'] = pools ret_value['pools'] = pools
policy_gen = (policy for policy in listener.l7policies if policy_gen = (policy for policy in listener.l7policies if
policy.provisioning_status != constants.PENDING_DELETE) policy.provisioning_status != constants.PENDING_DELETE)
@ -311,21 +313,27 @@ class JinjaTemplater(object):
break break
l7policies = [self._transform_l7policy( l7policies = [self._transform_l7policy(
x, feature_compatibility, tls_certs) x, feature_compatibility, tls_enabled, tls_certs)
for x in policy_gen] for x in policy_gen]
ret_value['l7policies'] = l7policies ret_value['l7policies'] = l7policies
return ret_value return ret_value
def _transform_pool(self, pool, feature_compatibility, 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 """Transforms a pool into an object that will
be processed by the templating system 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 = { ret_value = {
'id': pool.id, 'id': pool.id,
'protocol': PROTOCOL_MAP[pool.protocol], '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'), 'lb_algorithm': BALANCE_MAP.get(pool.lb_algorithm, 'roundrobin'),
'members': [], 'members': [],
'health_monitor': '', 'health_monitor': '',
@ -425,7 +433,7 @@ class JinjaTemplater(object):
} }
def _transform_l7policy(self, l7policy, feature_compatibility, 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 """Transforms an L7 policy into an object that will
be processed by the templating system be processed by the templating system
@ -446,7 +454,8 @@ class JinjaTemplater(object):
kwargs = {'pool_tls_certs': kwargs = {'pool_tls_certs':
tls_certs.get(l7policy.redirect_pool.id)} tls_certs.get(l7policy.redirect_pool.id)}
ret_value['redirect_pool'] = self._transform_pool( ret_value['redirect_pool'] = self._transform_pool(
l7policy.redirect_pool, feature_compatibility, **kwargs) l7policy.redirect_pool, feature_compatibility,
listener_tls_enabled, **kwargs)
else: else:
ret_value['redirect_pool'] = None ret_value['redirect_pool'] = None
if (l7policy.action in [constants.L7POLICY_ACTION_REDIRECT_TO_URL, if (l7policy.action in [constants.L7POLICY_ACTION_REDIRECT_TO_URL,

View File

@ -217,8 +217,14 @@ frontend {{ listener.id }}
{% else %} {% else %}
{% set persistence_opt = "" %} {% set persistence_opt = "" %}
{% endif %} {% endif %}
{% if pool.proxy_protocol %} {% if pool.proxy_protocol == 1 %}
{% set proxy_protocol_opt = " send-proxy" %} {% 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 %} {% else %}
{% set proxy_protocol_opt = "" %} {% set proxy_protocol_opt = "" %}
{% endif %} {% endif %}
@ -288,7 +294,7 @@ frontend {{ listener.id }}
{% macro backend_macro(constants, lib_consts, listener, pool, loadbalancer) %} {% macro backend_macro(constants, lib_consts, listener, pool, loadbalancer) %}
backend {{ pool.id }}:{{ listener.id }} backend {{ pool.id }}:{{ listener.id }}
{% if pool.protocol.lower() == constants.PROTOCOL_PROXY.lower() %} {% if pool.proxy_protocol is not none %}
mode {{ listener.protocol_mode }} mode {{ listener.protocol_mode }}
{% else %} {% else %}
mode {{ pool.protocol }} mode {{ pool.protocol }}

View File

@ -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'}
]
)

View File

@ -11,6 +11,7 @@
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations # License for the specific language governing permissions and limitations
# under the License. # under the License.
from octavia_lib.common import constants as lib_constants
from octavia.common import constants from octavia.common import constants
@ -280,4 +281,5 @@ INVALID_LISTENER_POOL_PROTOCOL_MAP = {
constants.PROTOCOL_HTTP, constants.PROTOCOL_HTTP,
constants.PROTOCOL_HTTPS, constants.PROTOCOL_HTTPS,
constants.PROTOCOL_TERMINATED_HTTPS, constants.PROTOCOL_TERMINATED_HTTPS,
constants.PROTOCOL_PROXY]} constants.PROTOCOL_PROXY,
lib_constants.PROTOCOL_PROXYV2]}

View File

@ -45,7 +45,7 @@ class TestRootController(base_db_test.OctaviaDBTestBase):
def test_api_versions(self): def test_api_versions(self):
versions = self._get_versions_with_config() versions = self._get_versions_with_config()
version_ids = tuple(v.get('id') for v in versions) 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.0', version_ids)
self.assertIn('v2.1', version_ids) self.assertIn('v2.1', version_ids)
self.assertIn('v2.2', 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.19', version_ids)
self.assertIn('v2.20', version_ids) self.assertIn('v2.20', version_ids)
self.assertIn('v2.21', 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 # Each version should have a 'self' 'href' to the API version URL
# [{u'rel': u'self', u'href': u'http://localhost/v2'}] # [{u'rel': u'self', u'href': u'http://localhost/v2'}]

View File

@ -1303,21 +1303,27 @@ class TestL7Policy(base.BaseAPITest):
self.set_object_status(self.lb_repo, self.lb_id) self.set_object_status(self.lb_repo, self.lb_id)
port = port + 1 port = port + 1
for pool_proto in invalid_map[listener_proto]: for pool_proto in invalid_map[listener_proto]:
pool = self.create_pool( if pool_proto == constants.PROTOCOL_TERMINATED_HTTPS:
self.lb_id, pool_proto, pool = self.create_pool(
constants.LB_ALGORITHM_ROUND_ROBIN).get('pool') self.lb_id, pool_proto,
self.set_object_status(self.lb_repo, self.lb_id) 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['listener_id'] = listener.get('id')
l7policy['redirect_pool_id'] = pool.get('id') l7policy['redirect_pool_id'] = pool.get('id')
expect_error_msg = ("Validation failure: The pool protocol " expect_error_msg = (
"'%s' is invalid while the listener " "Validation failure: The pool protocol '%s' is "
"protocol is '%s'.") % (pool_proto, "invalid while the listener protocol is '%s'.") % (
listener_proto) pool_proto, listener_proto)
res = self.post(self.L7POLICIES_PATH, res = self.post(self.L7POLICIES_PATH,
self._build_body(l7policy), status=400) self._build_body(l7policy), status=400)
self.assertEqual(expect_error_msg, res.json['faultstring']) self.assertEqual(expect_error_msg, res.json['faultstring'])
self.assert_correct_status(lb_id=self.lb_id) self.assert_correct_status(lb_id=self.lb_id)
@mock.patch('octavia.common.tls_utils.cert_parser.load_certificates_data') @mock.patch('octavia.common.tls_utils.cert_parser.load_certificates_data')
def test_listener_pool_protocol_map_put(self, mock_cert_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) self.set_object_status(self.lb_repo, self.lb_id)
port = port + 1 port = port + 1
for pool_proto in invalid_map[listener_proto]: for pool_proto in invalid_map[listener_proto]:
pool = self.create_pool( if pool_proto == constants.PROTOCOL_TERMINATED_HTTPS:
self.lb_id, pool_proto, pool = self.create_pool(
constants.LB_ALGORITHM_ROUND_ROBIN).get('pool') self.lb_id, pool_proto,
self.set_object_status(self.lb_repo, self.lb_id) constants.LB_ALGORITHM_ROUND_ROBIN, status=400)
l7policy = self.create_l7policy( self.assertIn("Invalid input", pool['faultstring'])
listener.get('id'), else:
constants.L7POLICY_ACTION_REJECT).get(self.root_tag) pool = self.create_pool(
self.set_object_status(self.lb_repo, self.lb_id) self.lb_id, pool_proto,
new_l7policy['redirect_pool_id'] = pool.get('id') constants.LB_ALGORITHM_ROUND_ROBIN).get('pool')
expect_error_msg = ("Validation failure: The pool protocol " self.set_object_status(self.lb_repo, self.lb_id)
"'%s' is invalid while the listener " l7policy = self.create_l7policy(
"protocol is '%s'.") % (pool_proto, listener.get('id'),
listener_proto) constants.L7POLICY_ACTION_REJECT).get(self.root_tag)
res = self.put(self.L7POLICY_PATH.format( self.set_object_status(self.lb_repo, self.lb_id)
l7policy_id=l7policy.get('id')), new_l7policy['redirect_pool_id'] = pool.get('id')
self._build_body(new_l7policy), status=400) expect_error_msg = (
self.assertEqual(expect_error_msg, res.json['faultstring']) "Validation failure: The pool protocol '%s' is "
self.assert_correct_status(lb_id=self.lb_id) "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)

View File

@ -2849,23 +2849,29 @@ class TestListener(base.BaseAPITest):
for listener_proto in invalid_map: for listener_proto in invalid_map:
for pool_proto in invalid_map[listener_proto]: for pool_proto in invalid_map[listener_proto]:
port = port + 1 port = port + 1
pool = self.create_pool( if pool_proto == constants.PROTOCOL_TERMINATED_HTTPS:
self.lb_id, pool_proto, pool = self.create_pool(
constants.LB_ALGORITHM_ROUND_ROBIN).get('pool') self.lb_id, pool_proto,
self.set_object_status(self.lb_repo, self.lb_id) constants.LB_ALGORITHM_ROUND_ROBIN, status=400)
expect_error_msg = ("Validation failure: The pool protocol " self.assertIn("Invalid input", pool['faultstring'])
"'%s' is invalid while the listener " else:
"protocol is '%s'.") % (pool_proto, pool = self.create_pool(
listener_proto) self.lb_id, pool_proto,
listener = {'protocol': listener_proto, constants.LB_ALGORITHM_ROUND_ROBIN).get('pool')
'protocol_port': port, self.set_object_status(self.lb_repo, self.lb_id)
'loadbalancer_id': self.lb_id, expect_error_msg = (
'default_pool_id': pool.get('id')} "Validation failure: The pool protocol '%s' is "
body = self._build_body(listener) "invalid while the listener protocol is '%s'.") % (
res = self.post(self.LISTENERS_PATH, body, pool_proto, listener_proto)
status=400, expect_errors=True) listener = {'protocol': listener_proto,
self.assertEqual(expect_error_msg, res.json['faultstring']) 'protocol_port': port,
self.assert_correct_status(lb_id=self.lb_id) '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') @mock.patch('octavia.common.tls_utils.cert_parser.load_certificates_data')
def test_listener_pool_protocol_map_put(self, mock_cert_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 " "'%s' is invalid while the listener "
"protocol is '%s'.") % (pool_proto, "protocol is '%s'.") % (pool_proto,
listener_proto) listener_proto)
pool = self.create_pool( if pool_proto == constants.PROTOCOL_TERMINATED_HTTPS:
self.lb_id, pool_proto, pool = self.create_pool(
constants.LB_ALGORITHM_ROUND_ROBIN).get('pool') self.lb_id, pool_proto,
self.set_object_status(self.lb_repo, self.lb_id) constants.LB_ALGORITHM_ROUND_ROBIN, status=400)
new_listener = {'default_pool_id': pool.get('id')} self.assertIn("Invalid input", pool['faultstring'])
res = self.put( else:
self.LISTENER_PATH.format(listener_id=listener.get('id')), pool = self.create_pool(
self._build_body(new_listener), status=400) self.lb_id, pool_proto,
self.assertEqual(expect_error_msg, res.json['faultstring']) constants.LB_ALGORITHM_ROUND_ROBIN).get('pool')
self.assert_correct_status(lb_id=self.lb_id) 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)

View File

@ -14,6 +14,7 @@
from unittest import mock from unittest import mock
from octavia_lib.common import constants as lib_constants
from oslo_config import cfg from oslo_config import cfg
from oslo_config import fixture as oslo_fixture from oslo_config import fixture as oslo_fixture
from oslo_utils import uuidutils from oslo_utils import uuidutils
@ -765,6 +766,30 @@ class TestPool(base.BaseAPITest):
lb_id=self.lb_id, listener_id=self.listener_id, lb_id=self.lb_id, listener_id=self.listener_id,
pool_id=api_pool.get('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): def test_create_sans_listener(self):
api_pool = self.create_pool( api_pool = self.create_pool(
self.lb_id, self.lb_id,
@ -2382,5 +2407,10 @@ class TestPool(base.BaseAPITest):
lb_pool['listener_id'] = listener.get('id') lb_pool['listener_id'] = listener.get('id')
res = self.post(self.POOLS_PATH, self._build_body(lb_pool), res = self.post(self.POOLS_PATH, self._build_body(lb_pool),
status=400, expect_errors=True) 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) self.assert_correct_status(lb_id=self.lb_id)

View File

@ -1288,18 +1288,18 @@ class TestHaproxyCfg(base.TestCase):
def test_transform_pool(self): def test_transform_pool(self):
in_pool = sample_configs_combined.sample_pool_tuple() 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) self.assertEqual(sample_configs_combined.RET_POOL_1, ret)
def test_transform_pool_2(self): def test_transform_pool_2(self):
in_pool = sample_configs_combined.sample_pool_tuple(sample_pool=2) 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) self.assertEqual(sample_configs_combined.RET_POOL_2, ret)
def test_transform_pool_http_reuse(self): def test_transform_pool_http_reuse(self):
in_pool = sample_configs_combined.sample_pool_tuple(sample_pool=2) in_pool = sample_configs_combined.sample_pool_tuple(sample_pool=2)
ret = self.jinja_cfg._transform_pool( 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 = copy.copy(sample_configs_combined.RET_POOL_2)
expected_config[constants.HTTP_REUSE] = True expected_config[constants.HTTP_REUSE] = True
self.assertEqual(expected_config, ret) self.assertEqual(expected_config, ret)
@ -1309,7 +1309,7 @@ class TestHaproxyCfg(base.TestCase):
cert_path = os.path.join(self.jinja_cfg.base_crt_dir, cert_path = os.path.join(self.jinja_cfg.base_crt_dir,
'test_listener_id', 'pool_cert.pem') 'test_listener_id', 'pool_cert.pem')
ret = self.jinja_cfg._transform_pool( 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 = copy.copy(sample_configs_combined.RET_POOL_1)
expected_config['client_cert'] = cert_path expected_config['client_cert'] = cert_path
self.assertEqual(expected_config, ret) self.assertEqual(expected_config, ret)
@ -1383,25 +1383,25 @@ class TestHaproxyCfg(base.TestCase):
def test_transform_l7policy(self): def test_transform_l7policy(self):
in_l7policy = sample_configs_combined.sample_l7policy_tuple( in_l7policy = sample_configs_combined.sample_l7policy_tuple(
'sample_l7policy_id_1') '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) self.assertEqual(sample_configs_combined.RET_L7POLICY_1, ret)
def test_transform_l7policy_2_8(self): def test_transform_l7policy_2_8(self):
in_l7policy = sample_configs_combined.sample_l7policy_tuple( in_l7policy = sample_configs_combined.sample_l7policy_tuple(
'sample_l7policy_id_2', sample_policy=2) '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) self.assertEqual(sample_configs_combined.RET_L7POLICY_2, ret)
# test invalid action without redirect_http_code # test invalid action without redirect_http_code
in_l7policy = sample_configs_combined.sample_l7policy_tuple( in_l7policy = sample_configs_combined.sample_l7policy_tuple(
'sample_l7policy_id_8', sample_policy=2, redirect_http_code=None) '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) self.assertEqual(sample_configs_combined.RET_L7POLICY_8, ret)
def test_transform_l7policy_disabled_rule(self): def test_transform_l7policy_disabled_rule(self):
in_l7policy = sample_configs_combined.sample_l7policy_tuple( in_l7policy = sample_configs_combined.sample_l7policy_tuple(
'sample_l7policy_id_6', sample_policy=6) '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) self.assertEqual(sample_configs_combined.RET_L7POLICY_6, ret)
def test_escape_haproxy_config_string(self): def test_escape_haproxy_config_string(self):

View File

@ -117,8 +117,9 @@ RET_MEMBER_3 = {
RET_POOL_1 = { RET_POOL_1 = {
'id': 'sample_pool_id_1', 'id': 'sample_pool_id_1',
'protocol': 'http', 'protocol': 'http',
'proxy_protocol': False, 'proxy_protocol': None,
'lb_algorithm': 'roundrobin', 'lb_algorithm': 'roundrobin',
'listener_tls_enabled': False,
'members': [RET_MEMBER_1, RET_MEMBER_2], 'members': [RET_MEMBER_1, RET_MEMBER_2],
'health_monitor': RET_MONITOR_1, 'health_monitor': RET_MONITOR_1,
'session_persistence': RET_PERSISTENCE, 'session_persistence': RET_PERSISTENCE,
@ -134,8 +135,9 @@ RET_POOL_1 = {
RET_POOL_2 = { RET_POOL_2 = {
'id': 'sample_pool_id_2', 'id': 'sample_pool_id_2',
'protocol': 'http', 'protocol': 'http',
'proxy_protocol': False, 'proxy_protocol': None,
'lb_algorithm': 'roundrobin', 'lb_algorithm': 'roundrobin',
'listener_tls_enabled': False,
'members': [RET_MEMBER_3], 'members': [RET_MEMBER_3],
'health_monitor': RET_MONITOR_2, 'health_monitor': RET_MONITOR_2,
'session_persistence': RET_PERSISTENCE, 'session_persistence': RET_PERSISTENCE,

View File

@ -0,0 +1,4 @@
---
features:
- |
Added support for proxy protocol version 2.