Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow all-ports listener (port 0), but only one per LB #24

Open
wants to merge 2 commits into
base: stable/yoga-m3
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions octavia/api/v2/controllers/listener.py
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,26 @@ def _test_lb_and_listener_statuses(
raise exceptions.ImmutableObject(resource=db_lb._name(),
id=lb_id)

def _validate_allports_listeners(self, db_listener):
"""If the listener listens on all ports (that is, it's port is set to 0), check that there is not already
another listener defined on the load balancer. Else, check that no other listener has it's port set to 0. """

# If listener creation happened to be fast enough before this method was called, the listener we're checking
# right now might end up already existing in the argument. Therefore we need to filter it out,
# else the validation fails if this listener is all-ports.
existing_listeners = [li for li in db_listener.load_balancer.listeners if li.id != db_listener.id]

# if there are no listeners yet we don't have a problem
if len(existing_listeners) == 0:
return

if db_listener.protocol_port == 0:
raise exceptions.ValidationException(
detail=_("Cannot create listener for all ports: Other listeners exist"))
if any(listener.protocol_port == 0 for listener in existing_listeners):
raise exceptions.ValidationException(
detail=_("Cannot create listener: A listener for all ports exists"))

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(
Expand Down Expand Up @@ -336,6 +356,7 @@ def _validate_create_listener(self, lock_session, listener_dict):
try:
db_listener = self.repositories.listener.create(
lock_session, **listener_dict)
self._validate_allports_listeners(db_listener)
if sni_containers:
for container in sni_containers:
sni_dict = {'listener_id': db_listener.id,
Expand Down
10 changes: 10 additions & 0 deletions octavia/api/v2/controllers/load_balancer.py
Original file line number Diff line number Diff line change
Expand Up @@ -630,6 +630,9 @@ def _graph_create(self, session, lock_session, db_lb, listeners, pools):
raise exceptions.QuotaException(
resource=data_models.Listener._name())

# Check that listeners don't conflict with each other
self._validate_internal_listener_conflicts(listeners)

# Now create all of the listeners
new_lists = []
for li in listeners:
Expand Down Expand Up @@ -775,6 +778,13 @@ def _lookup(self, id, *remainder):
return LoadBalancerMigrationController(lb_id=id), remainder
return None

def _validate_internal_listener_conflicts(self, listeners):
"""A load balancer must not have more than one all-ports listeners. If there are two or more listeners and
one of them is an all-ports listener, throw an error. """
if len(listeners) >= 2 and any(li['protocol_port'] == 0 for li in listeners):
raise exceptions.ValidationException(detail=_("Cannot create more than one listener: At least one "
"all-ports listener was specified"))


class StatusController(base.BaseController):
RBAC_TYPE = constants.RBAC_LOADBALANCER
Expand Down
4 changes: 2 additions & 2 deletions octavia/api/v2/types/listener.py
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@ class ListenerPOST(BaseListenerType):
protocol = wtypes.wsattr(wtypes.Enum(str, *constants.SUPPORTED_PROTOCOLS),
mandatory=True)
protocol_port = wtypes.wsattr(
wtypes.IntegerType(minimum=constants.MIN_PORT_NUMBER,
wtypes.IntegerType(minimum=constants.MIN_PORT_NUMBER_LISTENER,
maximum=constants.MAX_PORT_NUMBER), mandatory=True)
connection_limit = wtypes.wsattr(
wtypes.IntegerType(minimum=constants.MIN_CONNECTION_LIMIT),
Expand Down Expand Up @@ -208,7 +208,7 @@ class ListenerSingleCreate(BaseListenerType):
protocol = wtypes.wsattr(wtypes.Enum(str, *constants.SUPPORTED_PROTOCOLS),
mandatory=True)
protocol_port = wtypes.wsattr(
wtypes.IntegerType(minimum=constants.MIN_PORT_NUMBER,
wtypes.IntegerType(minimum=constants.MIN_PORT_NUMBER_LISTENER,
maximum=constants.MAX_PORT_NUMBER), mandatory=True)
connection_limit = wtypes.wsattr(
wtypes.IntegerType(minimum=constants.MIN_CONNECTION_LIMIT),
Expand Down
1 change: 1 addition & 0 deletions octavia/common/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -241,6 +241,7 @@

# API Integer Ranges
MIN_PORT_NUMBER = 1
MIN_PORT_NUMBER_LISTENER = 0 # A value of 0 means the listener receives on all ports. This is a F5 extension.
MAX_PORT_NUMBER = 65535

DEFAULT_CONNECTION_LIMIT = -1
Expand Down
17 changes: 17 additions & 0 deletions octavia/tests/functional/api/v2/test_listener.py
Original file line number Diff line number Diff line change
Expand Up @@ -2109,6 +2109,23 @@ def test_create_listeners_tcp_udp_same_port(self):
body = self._build_body(listener2_post)
self.post(self.LISTENERS_PATH, body, status=201)

def test_create_allports_listener(self):
listener_post = {'protocol': constants.PROTOCOL_TCP,
'protocol_port': 0,
'loadbalancer_id': self.lb_id}
body = self._build_body(listener_post)
self.post(self.LISTENERS_PATH, body, status=201)

def test_create_allports_listener_multiple(self):
listener1 = self.create_listener(constants.PROTOCOL_TCP, 80,
self.lb_id)
self.set_lb_status(self.lb_id)
listener2_post = {'protocol': constants.PROTOCOL_TCP,
'protocol_port': 0,
'loadbalancer_id': self.lb_id}
body = self._build_body(listener2_post)
self.post(self.LISTENERS_PATH, body, status=400)

def test_delete(self):
listener = self.create_listener(constants.PROTOCOL_HTTP, 80,
self.lb_id)
Expand Down