Browse Source

Add listener and pool protocol validation

The pool and listener can't be combined arbitrarily. We need to add
some constraints in protocol side.

Story: 2003500
Tasks: 24777

Co-Authored-By: Carlos Goncalves <cgoncalves@redhat.com>
Change-Id: Ifed862639d3fc3de23ace4c7ceaea1a4eca62749
(cherry picked from commit 47e0ef31bcfd34838fac4c619b699edd65e20223)
(cherry picked from commit 87704d42f42156bbb60138b86e34ea86f84629fe)
(cherry picked from commit 1f37a73eb96f8115abf9da2243dc84a6ca9ade1d)
(cherry picked from commit 34545524b6cc194c788172aaf3e565513f2e4d74)
changes/02/702102/1
Yang JianFeng Carlos Goncalves 1 year ago
parent
commit
e3ddec776d
14 changed files with 396 additions and 16 deletions
  1. +10
    -5
      api-ref/source/parameters.yaml
  2. +52
    -0
      api-ref/source/v2/general.inc
  3. +13
    -0
      octavia/api/v2/controllers/base.py
  4. +11
    -6
      octavia/api/v2/controllers/l7policy.py
  5. +7
    -4
      octavia/api/v2/controllers/listener.py
  6. +1
    -0
      octavia/api/v2/controllers/pool.py
  7. +7
    -0
      octavia/common/constants.py
  8. +12
    -0
      octavia/tests/common/constants.py
  9. +6
    -0
      octavia/tests/functional/api/v2/base.py
  10. +116
    -0
      octavia/tests/functional/api/v2/test_l7policy.py
  11. +99
    -0
      octavia/tests/functional/api/v2/test_listener.py
  12. +1
    -1
      octavia/tests/functional/api/v2/test_load_balancer.py
  13. +56
    -0
      octavia/tests/functional/api/v2/test_pool.py
  14. +5
    -0
      releasenotes/notes/add-protocol-validation-0f9129a045e372ce.yaml

+ 10
- 5
api-ref/source/parameters.yaml View File

@@ -233,13 +233,15 @@ created_at:
type: string
default_pool_id:
description: |
The ID of the pool used by the listener if no L7 policies match.
The ID of the pool used by the listener if no L7 policies match. The pool
has some restrictions. See :ref:`valid_protocol`.
in: body
required: true
type: uuid
default_pool_id-optional:
description: |
The ID of the pool used by the listener if no L7 policies match.
The ID of the pool used by the listener if no L7 policies match. The pool
has some restrictions. See :ref:`valid_protocol`.
in: body
required: false
type: uuid
@@ -492,14 +494,16 @@ l7policy-position-optional:
l7policy-redirect-pool_id:
description: |
Requests matching this policy will be redirected to the pool with this ID.
Only valid if ``action`` is ``REDIRECT_TO_POOL``.
Only valid if ``action`` is ``REDIRECT_TO_POOL``. The pool has some
restrictions, See :ref:`valid_protocol`.
in: body
required: true
type: uuid
l7policy-redirect-pool_id-optional:
description: |
Requests matching this policy will be redirected to the pool with this ID.
Only valid if ``action`` is ``REDIRECT_TO_POOL``.
Only valid if ``action`` is ``REDIRECT_TO_POOL``. The pool has some
restrictions, See :ref:`valid_protocol`.
in: body
required: false
type: uuid
@@ -639,7 +643,8 @@ listener-id:
listener-id-pool-optional:
description: |
The ID of the listener for the pool. Either ``listener_id`` or
``loadbalancer_id`` must be specified.
``loadbalancer_id`` must be specified. The listener has some restrictions,
See :ref:`valid_protocol`.
in: body
required: false
type: uuid


+ 52
- 0
api-ref/source/v2/general.inc View File

@@ -507,3 +507,55 @@ provisioning status once the asynchronus operation completes.

An entity in ``ERROR`` has failed provisioning. The entity may be deleted and
recreated.


.. _valid_protocol:

Protocol Combinations
=====================

The listener and pool can be associated through the listener's
``default_pool_id`` or l7policy's ``redirect_pool_id``. Both listener and pool
must set the protocol parameter. But the association between the listener and
the pool isn't arbitrarily and has some constraints at the protocol aspect.

Valid protocol combinations
---------------------------

.. |1| unicode:: U+2002 .. nut (&ensp;)
.. |2| unicode:: U+2003 .. mutton (&emsp;)
.. |listener| replace:: |2| |2| Listener
.. |1Y| replace:: |1| Y
.. |1N| replace:: |1| N
.. |2Y| replace:: |2| Y
.. |2N| replace:: |2| N
.. |8Y| replace:: |2| |2| |2| |2| Y
.. |8N| replace:: |2| |2| |2| |2| N

+-------------+-------+--------+------+-------------------+------+
|| |listener| || HTTP || HTTPS || TCP || TERMINATED_HTTPS || UDP |
|| Pool || || || || || |
+=============+=======+========+======+===================+======+
| HTTP | |2Y| | |2N| | |1Y| | |8Y| | |1N| |
+-------------+-------+--------+------+-------------------+------+
| HTTPS | |2N| | |2Y| | |1Y| | |8N| | |1N| |
+-------------+-------+--------+------+-------------------+------+
| PROXY | |2Y| | |2Y| | |1Y| | |8Y| | |1N| |
+-------------+-------+--------+------+-------------------+------+
| TCP | |2N| | |2Y| | |1Y| | |8N| | |1N| |
+-------------+-------+--------+------+-------------------+------+
| UDP | |2N| | |2N| | |1N| | |8N| | |1Y| |
+-------------+-------+--------+------+-------------------+------+

"Y" means the combination is valid and "N" means invalid.

The HTTPS protocol is HTTPS pass-through. For most providers, this is treated
as a TCP protocol. Some advanced providers may support HTTPS session
persistence features by using the session ID. The Amphora provider treats
HTTPS as a TCP flow, but currently does not support HTTPS session persistence
using the session ID.

The pool protocol of PROXY will use the listener protocol as the pool protocol
but will wrap that protocol in the proxy protocol. In the case of listener
protocol TERMINATED_HTTPS, a pool protocol of PROXY will be HTTP wrapped in the
proxy protocol.

+ 13
- 0
octavia/api/v2/controllers/base.py View File

@@ -23,6 +23,7 @@ from octavia.common import data_models
from octavia.common import exceptions
from octavia.common import policy
from octavia.db import repositories
from octavia.i18n import _

CONF = cfg.CONF
LOG = logging.getLogger(__name__)
@@ -196,3 +197,15 @@ class BaseController(rest.RestController):
attrs = [attr for attr in dir(obj) if not callable(
getattr(obj, attr)) and not attr.startswith("_")]
return attrs

@staticmethod
def _validate_protocol(listener_protocol, pool_protocol):
proto_map = constants.VALID_LISTENER_POOL_PROTOCOL_MAP
for valid_pool_proto in proto_map[listener_protocol]:
if pool_protocol == valid_pool_proto:
return
detail = _("The pool protocol '%(pool_protocol)s' is invalid while "
"the listener protocol is '%(listener_protocol)s'.") % {
"pool_protocol": pool_protocol,
"listener_protocol": listener_protocol}
raise exceptions.ValidationException(detail=detail)

+ 11
- 6
octavia/api/v2/controllers/l7policy.py View File

@@ -144,16 +144,17 @@ class L7PolicyController(base.BaseController):
"""Creates a l7policy on a listener."""
l7policy = l7policy_.l7policy
context = pecan.request.context.get('octavia_context')
# Make sure any pool specified by redirect_pool_id exists
if l7policy.redirect_pool_id:
self._get_db_pool(
context.session, l7policy.redirect_pool_id)
# Verify the parent listener exists
listener_id = l7policy.listener_id
listener = self._get_db_listener(
context.session, listener_id)
load_balancer_id = listener.load_balancer_id
l7policy.project_id = listener.project_id
# Make sure any pool specified by redirect_pool_id exists
if l7policy.redirect_pool_id:
db_pool = self._get_db_pool(
context.session, l7policy.redirect_pool_id)
self._validate_protocol(listener.protocol, db_pool.protocol)

self._auth_validate_action(context, l7policy.project_id,
constants.RBAC_POST)
@@ -216,11 +217,15 @@ class L7PolicyController(base.BaseController):
l7policy_dict[attr] = l7policy_dict.pop(val)
sanitized_l7policy = l7policy_types.L7PolicyPUT(**l7policy_dict)
context = pecan.request.context.get('octavia_context')

db_l7policy = self._get_db_l7policy(context.session, id)
listener = self._get_db_listener(
context.session, db_l7policy.listener_id)
# Make sure any specified redirect_pool_id exists
if l7policy_dict.get('redirect_pool_id'):
self._get_db_pool(
db_pool = self._get_db_pool(
context.session, l7policy_dict['redirect_pool_id'])
db_l7policy = self._get_db_l7policy(context.session, id)
self._validate_protocol(listener.protocol, db_pool.protocol)
load_balancer_id, listener_id = self._get_listener_and_loadbalancer_id(
db_l7policy)



+ 7
- 4
octavia/api/v2/controllers/listener.py View File

@@ -121,13 +121,14 @@ class ListenersController(base.BaseController):
raise exceptions.ImmutableObject(resource=db_lb._name(),
id=lb_id)

def _validate_pool(self, session, lb_id, pool_id):
def _validate_pool(self, session, lb_id, pool_id, listener_protocol):
"""Validate pool given exists on same load balancer as listener."""
db_pool = self.repositories.pool.get(
session, load_balancer_id=lb_id, id=pool_id)
if not db_pool:
raise exceptions.NotFound(
resource=data_models.Pool._name(), id=pool_id)
self._validate_protocol(listener_protocol, db_pool.protocol)

def _reset_lb_status(self, session, lb_id):
# Setting LB back to active because this should be a recoverable error
@@ -246,7 +247,8 @@ class ListenersController(base.BaseController):

if listener_dict['default_pool_id']:
self._validate_pool(context.session, load_balancer_id,
listener_dict['default_pool_id'])
listener_dict['default_pool_id'],
listener.protocol)

self._test_lb_and_listener_statuses(
lock_session, lb_id=load_balancer_id)
@@ -268,7 +270,8 @@ class ListenersController(base.BaseController):
l7policies = listener_dict.pop('l7policies', l7policies)
if listener_dict.get('default_pool_id'):
self._validate_pool(lock_session, load_balancer_id,
listener_dict['default_pool_id'])
listener_dict['default_pool_id'],
listener_dict['protocol'])
db_listener = self._validate_create_listener(
lock_session, listener_dict)

@@ -326,7 +329,7 @@ class ListenersController(base.BaseController):

if listener.default_pool_id:
self._validate_pool(context.session, load_balancer_id,
listener.default_pool_id)
listener.default_pool_id, db_listener.protocol)
self._test_lb_and_listener_statuses(context.session, load_balancer_id,
id=id)



+ 1
- 0
octavia/api/v2/controllers/pool.py View File

@@ -165,6 +165,7 @@ class PoolsController(base.BaseController):
elif pool.listener_id:
listener = self.repositories.listener.get(
context.session, id=pool.listener_id)
self._validate_protocol(listener.protocol, pool.protocol)
pool.project_id = listener.project_id
pool.loadbalancer_id = listener.load_balancer_id
else:


+ 7
- 0
octavia/common/constants.py View File

@@ -64,6 +64,13 @@ PROTOCOL_PROXY = 'PROXY'
SUPPORTED_PROTOCOLS = (PROTOCOL_TCP, PROTOCOL_HTTPS, PROTOCOL_HTTP,
PROTOCOL_TERMINATED_HTTPS, PROTOCOL_PROXY)

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]}

# API Integer Ranges
MIN_PORT_NUMBER = 1
MAX_PORT_NUMBER = 65535


+ 12
- 0
octavia/tests/common/constants.py View File

@@ -12,6 +12,8 @@
# License for the specific language governing permissions and limitations
# under the License.

from octavia.common import constants


class MockNovaInterface(object):
net_id = None
@@ -213,3 +215,13 @@ MOCK_NETWORK_IP_AVAILABILITY = {'network_ip_availability': (
'total_ips': MOCK_NETWORK_TOTAL_IPS,
'used_ips': MOCK_NETWORK_USED_IPS,
'subnet_ip_availability': MOCK_SUBNET_IP_AVAILABILITY})}

INVALID_LISTENER_POOL_PROTOCOL_MAP = {
constants.PROTOCOL_HTTP: [constants.PROTOCOL_HTTPS,
constants.PROTOCOL_TCP,
constants.PROTOCOL_TERMINATED_HTTPS],
constants.PROTOCOL_HTTPS: [constants.PROTOCOL_HTTP,
constants.PROTOCOL_TERMINATED_HTTPS],
constants.PROTOCOL_TCP: [constants.PROTOCOL_TERMINATED_HTTPS],
constants.PROTOCOL_TERMINATED_HTTPS: [constants.PROTOCOL_HTTPS,
constants.PROTOCOL_TCP]}

+ 6
- 0
octavia/tests/functional/api/v2/base.py View File

@@ -304,6 +304,9 @@ class BaseAPITest(base_db_test.OctaviaDBTestBase):
response = self.put(path, body, status=202)
return response.json

# NOTE: This method should be used cautiously. On load balancers with a
# significant amount of children resources, it will update the status for
# each and every resource and thus taking a lot of DB time.
def _set_lb_and_children_statuses(self, lb_id, prov_status, op_status,
autodetect=True):
self.set_object_status(self.lb_repo, lb_id,
@@ -373,6 +376,9 @@ class BaseAPITest(base_db_test.OctaviaDBTestBase):
provisioning_status=hm_prov,
operating_status=op_status)

# NOTE: This method should be used cautiously. On load balancers with a
# significant amount of children resources, it will update the status for
# each and every resource and thus taking a lot of DB time.
def set_lb_status(self, lb_id, status=None):
explicit_status = True if status is not None else False
if not explicit_status:


+ 116
- 0
octavia/tests/functional/api/v2/test_l7policy.py View File

@@ -21,6 +21,7 @@ from oslo_utils import uuidutils
from octavia.common import constants
import octavia.common.context
from octavia.common import data_models
from octavia.tests.common import constants as c_const
from octavia.tests.functional.api.v2 import base


@@ -1029,3 +1030,118 @@ class TestL7Policy(base.BaseAPITest):
self.delete(self.L7POLICY_PATH.format(
l7policy_id=l7policy.get('id')),
status=204)

@mock.patch('octavia.common.tls_utils.cert_parser.load_certificate_data')
def test_listener_pool_protocol_map_post(self, mock_cert_data):
cert = data_models.TLSContainer(certificate='cert')
mock_cert_data.return_value = {'sni_certs': [cert]}
valid_map = constants.VALID_LISTENER_POOL_PROTOCOL_MAP
port = 1
l7policy = {'action': constants.L7POLICY_ACTION_REDIRECT_TO_POOL}
for listener_proto in valid_map:
for pool_proto in valid_map[listener_proto]:
port = port + 1
opts = {}
if listener_proto == constants.PROTOCOL_TERMINATED_HTTPS:
opts['sni_container_refs'] = [uuidutils.generate_uuid()]
listener = self.create_listener(
listener_proto, port, self.lb_id, **opts).get('listener')
self.set_object_status(self.lb_repo, self.lb_id)
pool = self.create_pool(
self.lb_id, pool_proto,
constants.LB_ALGORITHM_ROUND_ROBIN).get('pool')

l7policy['listener_id'] = listener.get('id')
l7policy['redirect_pool_id'] = pool.get('id')
self.set_object_status(self.lb_repo, self.lb_id)
self.post(self.L7POLICIES_PATH,
self._build_body(l7policy), status=201)
self.set_object_status(self.lb_repo, self.lb_id)

invalid_map = c_const.INVALID_LISTENER_POOL_PROTOCOL_MAP
port = 100
for listener_proto in invalid_map:
opts = {}
if listener_proto == constants.PROTOCOL_TERMINATED_HTTPS:
opts['sni_container_refs'] = [uuidutils.generate_uuid()]
listener = self.create_listener(
listener_proto, port, self.lb_id, **opts).get('listener')
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['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_certificate_data')
def test_listener_pool_protocol_map_put(self, mock_cert_data):
cert = data_models.TLSContainer(certificate='cert')
mock_cert_data.return_value = {'sni_certs': [cert]}
valid_map = constants.VALID_LISTENER_POOL_PROTOCOL_MAP
port = 1
new_l7policy = {'action': constants.L7POLICY_ACTION_REDIRECT_TO_POOL}
for listener_proto in valid_map:
for pool_proto in valid_map[listener_proto]:
port = port + 1
opts = {}
if listener_proto == constants.PROTOCOL_TERMINATED_HTTPS:
opts['sni_container_refs'] = [uuidutils.generate_uuid()]
listener = self.create_listener(
listener_proto, port, self.lb_id, **opts).get('listener')
self.set_object_status(self.lb_repo, self.lb_id)
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')

self.put(
self.L7POLICY_PATH.format(l7policy_id=l7policy.get('id')),
self._build_body(new_l7policy), status=200)
self.set_object_status(self.lb_repo, self.lb_id)

invalid_map = c_const.INVALID_LISTENER_POOL_PROTOCOL_MAP
port = 100
for listener_proto in invalid_map:
opts = {}
if listener_proto == constants.PROTOCOL_TERMINATED_HTTPS:
opts['sni_container_refs'] = [uuidutils.generate_uuid()]
listener = self.create_listener(
listener_proto, port, self.lb_id, **opts).get('listener')
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)

+ 99
- 0
octavia/tests/functional/api/v2/test_listener.py View File

@@ -23,6 +23,7 @@ from oslo_utils import uuidutils
from octavia.common import constants
import octavia.common.context
from octavia.common import data_models
from octavia.tests.common import constants as c_const
from octavia.tests.functional.api.v2 import base


@@ -1350,3 +1351,101 @@ class TestListener(base.BaseAPITest):
listener_id=li['id'] + "/stats"), status=403)
self.conf.config(group='api_settings', auth_strategy=auth_strategy)
self.assertEqual(self.NOT_AUTHORIZED_BODY, res.json)

@mock.patch('octavia.common.tls_utils.cert_parser.load_certificate_data')
def test_listener_pool_protocol_map_post(self, mock_cert_data):
cert = data_models.TLSContainer(certificate='cert')
mock_cert_data.return_value = {'sni_certs': [cert]}
valid_map = constants.VALID_LISTENER_POOL_PROTOCOL_MAP
port = 1
for listener_proto in valid_map:
for pool_proto in valid_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)
listener = {'protocol': listener_proto,
'protocol_port': port,
'loadbalancer_id': self.lb_id,
'default_pool_id': pool.get('id')}
if listener_proto == constants.PROTOCOL_TERMINATED_HTTPS:
listener.update(
{'sni_container_refs': [uuidutils.generate_uuid()]})
body = self._build_body(listener)
self.post(self.LISTENERS_PATH, body, status=201)
self.set_object_status(self.lb_repo, self.lb_id)

invalid_map = c_const.INVALID_LISTENER_POOL_PROTOCOL_MAP
port = 1
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)

@mock.patch('octavia.common.tls_utils.cert_parser.load_certificate_data')
def test_listener_pool_protocol_map_put(self, mock_cert_data):
cert = data_models.TLSContainer(certificate='cert')
mock_cert_data.return_value = {'sni_certs': [cert]}
valid_map = constants.VALID_LISTENER_POOL_PROTOCOL_MAP
port = 1
for listener_proto in valid_map:
for pool_proto in valid_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)
opts = {}
if listener_proto == constants.PROTOCOL_TERMINATED_HTTPS:
opts['sni_container_refs'] = [uuidutils.generate_uuid()]
listener = self.create_listener(
listener_proto, port, self.lb_id, **opts).get('listener')
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=200)
self.set_object_status(self.lb_repo, self.lb_id)

invalid_map = c_const.INVALID_LISTENER_POOL_PROTOCOL_MAP
port = 100
for listener_proto in invalid_map:
opts = {}
if listener_proto == constants.PROTOCOL_TERMINATED_HTTPS:
opts['sni_container_refs'] = [uuidutils.generate_uuid()]
listener = self.create_listener(
listener_proto, port, self.lb_id, **opts).get('listener')
self.set_object_status(self.lb_repo, self.lb_id)
port = port + 1
for pool_proto in invalid_map[listener_proto]:
expect_error_msg = ("Validation failure: The pool protocol "
"'%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)

+ 1
- 1
octavia/tests/functional/api/v2/test_load_balancer.py View File

@@ -2473,7 +2473,7 @@ class TestLoadBalancerGraph(base.BaseAPITest):
expected_members=[expected_member],
create_hm=create_hm,
expected_hm=expected_hm,
protocol=constants.PROTOCOL_TCP)
protocol=constants.PROTOCOL_HTTP)
create_sni_containers, expected_sni_containers = (
self._get_sni_container_bodies())
create_l7rules, expected_l7rules = self._get_l7rules_bodies()


+ 56
- 0
octavia/tests/functional/api/v2/test_pool.py View File

@@ -21,6 +21,7 @@ from oslo_utils import uuidutils
from octavia.common import constants
import octavia.common.context
from octavia.common import data_models
from octavia.tests.common import constants as c_const
from octavia.tests.functional.api.v2 import base


@@ -1411,3 +1412,58 @@ class TestPool(base.BaseAPITest):
self.set_lb_status(self.lb_id, status=constants.DELETED)
self.delete(self.POOL_PATH.format(pool_id=api_pool.get('id')),
status=204)

@mock.patch('octavia.common.tls_utils.cert_parser.load_certificate_data')
def test_valid_listener_pool_protocol(self, mock_cert_data):
cert = data_models.TLSContainer(certificate='cert')
lb_pool = {
'lb_algorithm': constants.LB_ALGORITHM_ROUND_ROBIN,
'project_id': self.project_id}
mock_cert_data.return_value = {'sni_certs': [cert]}
valid_map = constants.VALID_LISTENER_POOL_PROTOCOL_MAP
port = 1
for listener_proto in valid_map:
for pool_proto in valid_map[listener_proto]:
port = port + 1
opts = {}
if listener_proto == constants.PROTOCOL_TERMINATED_HTTPS:
opts['sni_container_refs'] = [uuidutils.generate_uuid()]
listener = self.create_listener(
listener_proto, port, self.lb_id, **opts).get('listener')
self.set_object_status(self.lb_repo, self.lb_id)
if listener['default_pool_id'] is None:
lb_pool['protocol'] = pool_proto
lb_pool['listener_id'] = listener.get('id')
self.post(self.POOLS_PATH, self._build_body(lb_pool),
status=201)
self.set_object_status(self.lb_repo, self.lb_id)

@mock.patch('octavia.common.tls_utils.cert_parser.load_certificate_data')
def test_invalid_listener_pool_protocol_map(self, mock_cert_data):
cert = data_models.TLSContainer(certificate='cert')
lb_pool = {
'lb_algorithm': constants.LB_ALGORITHM_ROUND_ROBIN,
'project_id': self.project_id}
mock_cert_data.return_value = {'sni_certs': [cert]}
invalid_map = c_const.INVALID_LISTENER_POOL_PROTOCOL_MAP
port = 1
for listener_proto in invalid_map:
opts = {}
if listener_proto == constants.PROTOCOL_TERMINATED_HTTPS:
opts['sni_container_refs'] = [uuidutils.generate_uuid()]
listener = self.create_listener(
listener_proto, port, self.lb_id, **opts).get('listener')
self.set_object_status(self.lb_repo, self.lb_id)
port = port + 1
for pool_proto in invalid_map[listener_proto]:
expect_error_msg = ("Validation failure: The pool protocol "
"'%s' is invalid while the listener "
"protocol is '%s'.") % (pool_proto,
listener_proto)
if listener['default_pool_id'] is None:
lb_pool['protocol'] = pool_proto
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'])
self.assert_correct_status(lb_id=self.lb_id)

+ 5
- 0
releasenotes/notes/add-protocol-validation-0f9129a045e372ce.yaml View File

@@ -0,0 +1,5 @@
---
fixes:
- |
Add listener and pool protocol validation. The pool and listener can't be
combined arbitrarily. We need some constraints on the protocol side.

Loading…
Cancel
Save