From b42a64a00ef549c69bc6835db9234a889b5ba62c Mon Sep 17 00:00:00 2001 From: Gregory Thiemonge Date: Fri, 28 Jun 2019 15:23:32 +0200 Subject: [PATCH] Allow listeners for different protocols on the same port Added 'protocol' name in the unique constraint list for listeners, updated conflicting/duplicate entries detection in API. Added alembic migration script. Story: 2005070 Task: 29643 Change-Id: If85b59bddb8d6dc9916c3fef5155e838f1af63b6 --- octavia/api/v2/controllers/listener.py | 37 ++++++++++++++++++- octavia/common/constants.py | 10 +++++ octavia/common/exceptions.py | 3 +- ...62a99609a_add_protocol_in_listener_keys.py | 35 ++++++++++++++++++ octavia/db/models.py | 5 ++- .../tests/functional/api/v2/test_listener.py | 22 +++++++++++ .../same-port-listeners-41198368d470e821.yaml | 5 +++ 7 files changed, 113 insertions(+), 4 deletions(-) create mode 100644 octavia/db/migration/alembic_migrations/versions/a5762a99609a_add_protocol_in_listener_keys.py create mode 100644 releasenotes/notes/same-port-listeners-41198368d470e821.yaml diff --git a/octavia/api/v2/controllers/listener.py b/octavia/api/v2/controllers/listener.py index ea8763e648..f000ba72ec 100644 --- a/octavia/api/v2/controllers/listener.py +++ b/octavia/api/v2/controllers/listener.py @@ -229,6 +229,40 @@ class ListenersController(base.BaseController): listener_dict.get('client_ca_tls_certificate_id'), listener_dict.get('client_crl_container_id', None)) + # Validate that the L4 protocol (UDP or TCP) is not already used for + # the specified protocol_port in this load balancer + pcontext = pecan.request.context + query_filter = { + 'project_id': listener_dict['project_id'], + 'load_balancer_id': listener_dict['load_balancer_id'], + 'protocol_port': listener_dict['protocol_port'] + } + + # Get listeners on the same load balancer that use the same + # protocol port + db_listeners = self.repositories.listener.get_all_API_list( + lock_session, show_deleted=False, + pagination_helper=pcontext.get(constants.PAGINATION_HELPER), + **query_filter)[0] + + if db_listeners: + l4_protocol = constants.L4_PROTOCOL_MAP[listener_protocol] + + # List supported protocols that share the same L4 protocol as our + # new listener + disallowed_protocols = [ + p + for p in constants.L4_PROTOCOL_MAP + if constants.L4_PROTOCOL_MAP[p] == l4_protocol + ] + + for db_l in db_listeners: + # Check if l4 protocol ports conflict + if db_l.protocol in disallowed_protocols: + raise exceptions.DuplicateListenerEntry( + protocol=db_l.protocol, + port=listener_dict.get('protocol_port')) + try: db_listener = self.repositories.listener.create( lock_session, **listener_dict) @@ -242,13 +276,14 @@ class ListenersController(base.BaseController): lock_session, id=db_listener.id) return db_listener except odb_exceptions.DBDuplicateEntry as de: - column_list = ['load_balancer_id', 'protocol_port'] + column_list = ['load_balancer_id', 'protocol', 'protocol_port'] constraint_list = ['uq_listener_load_balancer_id_protocol_port'] if ['id'] == de.columns: raise exceptions.IDAlreadyExists() if (set(column_list) == set(de.columns) or set(constraint_list) == set(de.columns)): raise exceptions.DuplicateListenerEntry( + protocol=listener_dict.get('protocol'), port=listener_dict.get('protocol_port')) except odb_exceptions.DBError: raise exceptions.InvalidOption(value=listener_dict.get('protocol'), diff --git a/octavia/common/constants.py b/octavia/common/constants.py index 1155708ec6..c909177f08 100644 --- a/octavia/common/constants.py +++ b/octavia/common/constants.py @@ -671,3 +671,13 @@ TOPIC_AMPHORA_V2 = 'octavia_provisioning_v2' HAPROXY_HTTP_PROTOCOLS = [lib_consts.PROTOCOL_HTTP, lib_consts.PROTOCOL_TERMINATED_HTTPS] + +# Map each supported protocol to its L4 protocol +L4_PROTOCOL_MAP = { + PROTOCOL_TCP: PROTOCOL_TCP, + PROTOCOL_HTTP: PROTOCOL_TCP, + PROTOCOL_HTTPS: PROTOCOL_TCP, + PROTOCOL_TERMINATED_HTTPS: PROTOCOL_TCP, + PROTOCOL_PROXY: PROTOCOL_TCP, + PROTOCOL_UDP: PROTOCOL_UDP, +} diff --git a/octavia/common/exceptions.py b/octavia/common/exceptions.py index 5622821504..647bff73f6 100644 --- a/octavia/common/exceptions.py +++ b/octavia/common/exceptions.py @@ -147,7 +147,8 @@ class CertificateGenerationException(OctaviaException): class DuplicateListenerEntry(APIException): msg = _("Another Listener on this Load Balancer " - "is already using protocol_port %(port)d") + "is already using protocol %(protocol)s " + "and protocol_port %(port)d") code = 409 diff --git a/octavia/db/migration/alembic_migrations/versions/a5762a99609a_add_protocol_in_listener_keys.py b/octavia/db/migration/alembic_migrations/versions/a5762a99609a_add_protocol_in_listener_keys.py new file mode 100644 index 0000000000..c888de618d --- /dev/null +++ b/octavia/db/migration/alembic_migrations/versions/a5762a99609a_add_protocol_in_listener_keys.py @@ -0,0 +1,35 @@ +# Copyright (c) 2019 Red Hat, Inc. +# +# 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 protocol in listener keys + +Revision ID: a5762a99609a +Revises: 392fb85b4419 +Create Date: 2019-06-28 14:02:11.415292 + +""" + +from alembic import op + +# revision identifiers, used by Alembic. +revision = 'a5762a99609a' +down_revision = '392fb85b4419' + + +def upgrade(): + op.execute("ALTER TABLE `listener` " + "DROP INDEX `uq_listener_load_balancer_id_protocol_port`, " + "ADD UNIQUE KEY " + "`uq_listener_load_balancer_id_protocol_port` " + "(`load_balancer_id`, `protocol`, `protocol_port`)") diff --git a/octavia/db/models.py b/octavia/db/models.py index 7d2fe54f98..dd8e7fa644 100644 --- a/octavia/db/models.py +++ b/octavia/db/models.py @@ -455,8 +455,9 @@ class Listener(base_models.BASE, base_models.IdMixin, __v2_wsme__ = listener.ListenerResponse __table_args__ = ( - sa.UniqueConstraint('load_balancer_id', 'protocol_port', - name='uq_listener_load_balancer_id_protocol_port'), + sa.UniqueConstraint( + 'load_balancer_id', 'protocol', 'protocol_port', + name='uq_listener_load_balancer_id_protocol_port'), ) description = sa.Column(sa.String(255), nullable=True) diff --git a/octavia/tests/functional/api/v2/test_listener.py b/octavia/tests/functional/api/v2/test_listener.py index 1771e3d3eb..7a01fad6f8 100644 --- a/octavia/tests/functional/api/v2/test_listener.py +++ b/octavia/tests/functional/api/v2/test_listener.py @@ -1708,6 +1708,28 @@ class TestListener(base.BaseAPITest): body = self._build_body(listener2_post) self.post(self.LISTENERS_PATH, body, status=409) + def test_create_listeners_tcp_https_same_port(self): + listener1 = self.create_listener(constants.PROTOCOL_TCP, 80, + self.lb_id) + self.set_lb_status(self.lb_id) + listener2_post = {'protocol': constants.PROTOCOL_HTTPS, + 'protocol_port': + listener1['listener']['protocol_port'], + 'loadbalancer_id': self.lb_id} + body = self._build_body(listener2_post) + self.post(self.LISTENERS_PATH, body, status=409) + + def test_create_listeners_tcp_udp_same_port(self): + listener1 = self.create_listener(constants.PROTOCOL_TCP, 80, + self.lb_id) + self.set_lb_status(self.lb_id) + listener2_post = {'protocol': constants.PROTOCOL_UDP, + 'protocol_port': + listener1['listener']['protocol_port'], + 'loadbalancer_id': self.lb_id} + body = self._build_body(listener2_post) + self.post(self.LISTENERS_PATH, body, status=201) + def test_delete(self): listener = self.create_listener(constants.PROTOCOL_HTTP, 80, self.lb_id) diff --git a/releasenotes/notes/same-port-listeners-41198368d470e821.yaml b/releasenotes/notes/same-port-listeners-41198368d470e821.yaml new file mode 100644 index 0000000000..ad1b3fb7c7 --- /dev/null +++ b/releasenotes/notes/same-port-listeners-41198368d470e821.yaml @@ -0,0 +1,5 @@ +--- +fixes: + - | + Fixed bug which prevented the creation of listeners for different protocols + on the same port (i.e: tcp port 53, and udp port 53).