Add SCTP support in API

Add SCTP support in the API for listeners, pools, health-monitors
resources.

Story: 2007884
Task: 40255

Change-Id: I57a3c528a20943724bdcd36422c689f496068330
This commit is contained in:
Gregory Thiemonge
2020-09-08 19:08:34 +02:00
parent 4260d8a74b
commit 639c11751e
21 changed files with 385 additions and 98 deletions

View File

@@ -760,8 +760,8 @@ healthmonitor-timeout-optional:
type: integer type: integer
healthmonitor-type: healthmonitor-type:
description: | description: |
The type of health monitor. One of ``HTTP``, ``HTTPS``, ``PING``, ``TCP``, The type of health monitor. One of ``HTTP``, ``HTTPS``, ``PING``,
``TLS-HELLO``, or ``UDP-CONNECT``. ``SCTP``, ``TCP``, ``TLS-HELLO``, or ``UDP-CONNECT``.
in: body in: body
required: true required: true
type: string type: string
@@ -1228,15 +1228,15 @@ project_id-optional-deprecated:
type: string type: string
protocol: protocol:
description: | description: |
The protocol for the resource. One of ``HTTP``, ``HTTPS``, ``TCP``, The protocol for the resource. One of ``HTTP``, ``HTTPS``, ``SCTP``,
``TERMINATED_HTTPS``, or ``UDP``. ``TCP``, ``TERMINATED_HTTPS``, or ``UDP``.
in: body in: body
required: true required: true
type: string type: string
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``,
``PROXYV2``, ``TCP``, or ``UDP``. ``PROXYV2``, ``SCTP``, ``TCP``, or ``UDP``.
in: body in: body
required: true required: true
type: string type: string
@@ -1421,18 +1421,18 @@ session_persistence_cookie:
type: string type: string
session_persistence_granularity: session_persistence_granularity:
description: | description: |
The netmask used to determine UDP session persistence. Currently only The netmask used to determine SCTP or UDP session persistence. Currently
valid for UDP pools with session persistence of SOURCE_IP. Default netmask only valid for SCTP or UDP pools with session persistence of SOURCE_IP.
is 255.255.255.255, meaning per client full IP. Default netmask is 255.255.255.255, meaning per client full IP.
in: body in: body
min_version: 2.2 min_version: 2.2
required: false required: false
type: string type: string
session_persistence_timeout: session_persistence_timeout:
description: | description: |
The timeout, in seconds, after which a UDP flow may be rescheduled to a The timeout, in seconds, after which a SCTP or UDP flow may be rescheduled
different member. Currently only applies to UDP pools with session to a different member. Currently only applies to SCTP or UDP pools with
persistence of SOURCE_IP. Default is 360. session persistence of SOURCE_IP. Default is 360.
in: body in: body
min_version: 2.2 min_version: 2.2
required: false required: false

View File

@@ -594,20 +594,22 @@ Valid protocol combinations
.. |8Y| replace:: |2| |2| |2| |2| Y .. |8Y| replace:: |2| |2| |2| |2| Y
.. |8N| replace:: |2| |2| |2| |2| N .. |8N| replace:: |2| |2| |2| |2| N
+-------------+-------+--------+------+-------------------+------+ +-------------+-------+--------+-------+------+-------------------+------+
|| |listener| || HTTP || HTTPS || TCP || TERMINATED_HTTPS || UDP | || |listener| || HTTP || HTTPS || SCTP || TCP || TERMINATED_HTTPS || UDP |
|| Pool || || || || || | || Pool || || || || || || |
+=============+=======+========+======+===================+======+ +=============+=======+========+=======+======+===================+======+
| HTTP | |2Y| | |2N| | |1Y| | |8Y| | |1N| | | HTTP | |2Y| | |2N| | |2N| | |1Y| | |8Y| | |1N| |
+-------------+-------+--------+------+-------------------+------+ +-------------+-------+--------+-------+------+-------------------+------+
| HTTPS | |2N| | |2Y| | |1Y| | |8N| | |1N| | | HTTPS | |2N| | |2Y| | |2N| | |1Y| | |8N| | |1N| |
+-------------+-------+--------+------+-------------------+------+ +-------------+-------+--------+-------+------+-------------------+------+
| PROXY | |2Y| | |2Y| | |1Y| | |8Y| | |1N| | | PROXY | |2Y| | |2Y| | |2N| | |1Y| | |8Y| | |1N| |
+-------------+-------+--------+------+-------------------+------+ +-------------+-------+--------+-------+------+-------------------+------+
| TCP | |2N| | |2Y| | |1Y| | |8N| | |1N| | | SCTP | |2N| | |2N| | |2Y| | |1N| | |8N| | |1N| |
+-------------+-------+--------+------+-------------------+------+ +-------------+-------+--------+-------+------+-------------------+------+
| UDP | |2N| | |2N| | |1N| | |8N| | |1Y| | | TCP | |2N| | |2Y| | |2N| | |1Y| | |8N| | |1N| |
+-------------+-------+--------+------+-------------------+------+ +-------------+-------+--------+-------+------+-------------------+------+
| UDP | |2N| | |2N| | |2N| | |1N| | |8N| | |1Y| |
+-------------+-------+--------+-------+------+-------------------+------+
"Y" means the combination is valid and "N" means invalid. "Y" means the combination is valid and "N" means invalid.
@@ -640,25 +642,28 @@ Valid protocol combinations
.. |5Y| replace:: |2| |2| |1| Y .. |5Y| replace:: |2| |2| |1| Y
.. |5N| replace:: |2| |2| |1| N .. |5N| replace:: |2| |2| |1| N
+-------------------+-------+--------+-------+------+------------+---------------+ +-------------------+-------+--------+-------+-------+------+------------+---------------+
|| |Health Monitor| || HTTP || HTTPS || PING || TCP || TLS-HELLO || |UDPCONNECT| | || |Health Monitor| || HTTP || HTTPS || PING || SCTP || TCP || TLS-HELLO || |UDPCONNECT| |
|| Pool || || || || || || | || Pool || || || || || || || |
+===================+=======+========+=======+======+============+===============+ +===================+=======+========+=======+=======+======+============+===============+
| HTTP | |2Y| | |2Y| | |1Y| | |1Y| | |4Y| | |5N| | | HTTP | |2Y| | |2Y| | |1Y| | |1N| | |1Y| | |4Y| | |5N| |
+-------------------+-------+--------+-------+------+------------+---------------+ +-------------------+-------+--------+-------+-------+------+------------+---------------+
| HTTPS | |2Y| | |2Y| | |1Y| | |1Y| | |4Y| | |5N| | | HTTPS | |2Y| | |2Y| | |1Y| | |1N| | |1Y| | |4Y| | |5N| |
+-------------------+-------+--------+-------+------+------------+---------------+ +-------------------+-------+--------+-------+-------+------+------------+---------------+
| PROXY | |2Y| | |2Y| | |1Y| | |1Y| | |4Y| | |5N| | | PROXY | |2Y| | |2Y| | |1Y| | |1N| | |1Y| | |4Y| | |5N| |
+-------------------+-------+--------+-------+------+------------+---------------+ +-------------------+-------+--------+-------+-------+------+------------+---------------+
| TCP | |2Y| | |2Y| | |1Y| | |1Y| | |4Y| | |5N| | | SCTP | |2Y| | |2N| | |1N| | |1Y| | |1Y| | |4N| | |5Y| |
+-------------------+-------+--------+-------+------+------------+---------------+ +-------------------+-------+--------+-------+-------+------+------------+---------------+
| UDP | |2Y| | |2N| | |1N| | |1Y| | |4N| | |5Y| | | TCP | |2Y| | |2Y| | |1Y| | |1N| | |1Y| | |4Y| | |5N| |
+-------------------+-------+--------+-------+------+------------+---------------+ +-------------------+-------+--------+-------+-------+------+------------+---------------+
| UDP | |2Y| | |2N| | |1N| | |1Y| | |1Y| | |4N| | |5Y| |
+-------------------+-------+--------+-------+-------+------+------------+---------------+
"Y" means the combination is valid and "N" means invalid. "Y" means the combination is valid and "N" means invalid.
These combinations are mostly as you'd expect for all non-UDP pool protocols: These combinations are mostly as you'd expect for all non-UDP/SCTP pool
non-UDP pools can have health monitors with any check type besides UDP-CONNECT. protocols: non-UDP/SCTP pools can have health monitors with any check type
For UDP pools however, things are a little more complicated. UDP Pools support besides UDP-CONNECT and SCTP.
UDP-CONNECT but also HTTP and TCP checks. HTTPS checks are technically feasible For UDP or SCTP pools however, things are a little more complicated. UDP and
but have not yet been implemented. SCTP Pools support UDP-CONNECT and SCTP but also HTTP and TCP checks. HTTPS
checks are technically feasible but have not yet been implemented.

View File

@@ -118,7 +118,7 @@ At a minimum, you must specify these health monitor attributes:
times out. times out.
- ``type`` The type of health monitor. One of ``HTTP``, ``HTTPS``, ``PING``, - ``type`` The type of health monitor. One of ``HTTP``, ``HTTPS``, ``PING``,
``TCP``, ``TLS-HELLO``, or ``UDP-CONNECT``. ``SCTP``, ``TCP``, ``TLS-HELLO``, or ``UDP-CONNECT``.
Some attributes receive default values if you omit them from the request: Some attributes receive default values if you omit them from the request:

View File

@@ -115,7 +115,9 @@ L7 policies with ``action`` of ``REDIRECT_TO_URL`` will return the default HTTP
L7 policies with ``action`` of ``REJECT`` will return a ``Forbidden (403)`` L7 policies with ``action`` of ``REJECT`` will return a ``Forbidden (403)``
response code to the requester. response code to the requester.
.. note:: Pools of type ``UDP`` cannot be used in L7 policies at this time. .. note::
Pools of type ``SCTP``, ``TCP`` or ``UDP`` cannot be used in L7
policies at this time.
.. rest_status_code:: success ../http-status.yaml .. rest_status_code:: success ../http-status.yaml

View File

@@ -103,7 +103,7 @@ 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``, ``PROXYV2``, listen. A valid value is ``HTTP``, ``HTTPS``, ``PROXY``, ``PROXYV2``,
``TCP``, or ``UDP``. ``SCTP``, ``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

@@ -1201,7 +1201,7 @@ and validated with the following exceptions:
| | | be less than the delay value. | | | | be less than the delay value. |
+-----------------------+--------+------------------------------------------+ +-----------------------+--------+------------------------------------------+
| type | string | The type of health monitor. One of HTTP, | | type | string | The type of health monitor. One of HTTP, |
| | | HTTPS, PING, TCP, TLS-HELLO or | | | | HTTPS, PING, SCTP, TCP, TLS-HELLO or |
| | | UDP-CONNECT. | | | | UDP-CONNECT. |
+-----------------------+--------+------------------------------------------+ +-----------------------+--------+------------------------------------------+
| url_path | string | The HTTP URL path of the request sent by | | url_path | string | The HTTP URL path of the request sent by |

View File

@@ -159,6 +159,14 @@ cli=openstack loadbalancer healthmonitor create --type UDP-CONNECT <pool>
driver.amphora=complete driver.amphora=complete
driver.ovn=missing driver.ovn=missing
[operation.type.SCTP]
title=type - SCTP
status=optional
notes=Use SCTP for the health monitor.
cli=openstack loadbalancer healthmonitor create --type SCTP <pool>
driver.amphora=missing
driver.ovn=missing
[operation.url_path] [operation.url_path]
title=url_path title=url_path
status=optional status=optional

View File

@@ -230,6 +230,14 @@ cli=openstack loadbalancer listener create --protocol UDP <loadbalancer>
driver.amphora=complete driver.amphora=complete
driver.ovn=complete driver.ovn=complete
[operation.protocol.SCTP]
title=protocol - SCTP
status=optional
notes=SCTP protocol support for the listener.
cli=openstack loadbalancer listener create --protocol SCTP <loadbalancer>
driver.amphora=missing
driver.ovn=missing
[operation.protocol_port] [operation.protocol_port]
title=protocol_port title=protocol_port
status=mandatory status=mandatory

View File

@@ -138,6 +138,14 @@ cli=openstack loadbalancer pool create --protocol UDP --listener <listener>
driver.amphora=complete driver.amphora=complete
driver.ovn=complete driver.ovn=complete
[operation.protocol.SCTP]
title=protocol - SCTP
status=optional
notes=SCTP protocol support for the pool.
cli=openstack loadbalancer pool create --protocol SCTP --listener <listener>
driver.amphora=missing
driver.ovn=missing
[operation.session_persistence.APP_COOKIE] [operation.session_persistence.APP_COOKIE]
title=session_persistence - APP_COOKIE title=session_persistence - APP_COOKIE
status=optional status=optional
@@ -165,7 +173,7 @@ driver.ovn=missing
[operation.session_persistence.persistence_timeout] [operation.session_persistence.persistence_timeout]
title=session_persistence - persistence_timeout title=session_persistence - persistence_timeout
status=optional status=optional
notes=The timeout, in seconds, after which a UDP flow may be rescheduled to a different member. notes=The timeout, in seconds, after which a SCTP or UDP flow may be rescheduled to a different member.
cli=openstack loadbalancer pool create --session-persistence persistence_timeout=360 --listener <listener> cli=openstack loadbalancer pool create --session-persistence persistence_timeout=360 --listener <listener>
driver.amphora=complete driver.amphora=complete
driver.ovn=missing driver.ovn=missing
@@ -173,7 +181,7 @@ driver.ovn=missing
[operation.session_persistence.persistence_granularity] [operation.session_persistence.persistence_granularity]
title=session_persistence - persistence_granularity title=session_persistence - persistence_granularity
status=optional status=optional
notes=The netmask used to determine UDP SOURCE_IP session persistence. notes=The netmask used to determine SCTP or UDP SOURCE_IP session persistence.
cli=openstack loadbalancer pool create --session-persistence persistence_granularity=255.255.255.255 --listener <listener> cli=openstack loadbalancer pool create --session-persistence persistence_granularity=255.255.255.255 --listener <listener>
driver.amphora=complete driver.amphora=complete
driver.ovn=missing driver.ovn=missing

View File

@@ -856,8 +856,8 @@ generates the health check in your web application:
Other health monitors Other health monitors
--------------------- ---------------------
Other health monitor types include ``PING``, ``TCP``, ``HTTPS``, ``TLS-HELLO``, Other health monitor types include ``PING``, ``TCP``, ``HTTPS``, ``SCTP``,
and ``UDP-CONNECT``. ``TLS-HELLO``, and ``UDP-CONNECT``.
``PING`` health monitors send periodic ICMP PING requests to the back-end ``PING`` health monitors send periodic ICMP PING requests to the back-end
servers. Obviously, your back-end servers must be configured to allow PINGs in servers. Obviously, your back-end servers must be configured to allow PINGs in
@@ -881,6 +881,13 @@ ssl back-end servers. Unfortunately, this causes problems if the servers are
performing client certificate validation, as HAProxy won't have a valid cert. performing client certificate validation, as HAProxy won't have a valid cert.
In this case, using ``TLS-HELLO`` type monitoring is an alternative. In this case, using ``TLS-HELLO`` type monitoring is an alternative.
``SCTP`` health monitors send an INIT packet to the back-end server's port.
If an application is listening on this port, the Operating System should reply
with an INIT ACK packet, but if the port is closed, it replies with an ABORT
packet.
If the health monitor receives an INIT ACK packet, it immediatly closes the
connection with an ABORT packet, and considers that the server is ONLINE.
``TLS-HELLO`` health monitors simply ensure the back-end server responds to ``TLS-HELLO`` health monitors simply ensure the back-end server responds to
SSLv3 client hello messages. It will not check any other health metrics, like SSLv3 client hello messages. It will not check any other health metrics, like
status code or body contents. status code or body contents.

View File

@@ -131,6 +131,9 @@ class RootController(object):
self._add_a_version(versions, 'v2.21', 'v2', 'SUPPORTED', 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 # Add PROXYV2 pool protocol
self._add_a_version(versions, 'v2.22', 'v2', 'CURRENT', self._add_a_version(versions, 'v2.22', 'v2', 'SUPPORTED',
'2020-09-04T00:00:00Z', host_url) '2020-09-04T00:00:00Z', host_url)
# SCTP protocol
self._add_a_version(versions, 'v2.23', 'v2', 'CURRENT',
'2020-09-07T00:00:00Z', host_url)
return {'versions': versions} return {'versions': versions}

View File

@@ -14,6 +14,7 @@
# under the License. # under the License.
from octavia_lib.api.drivers import data_models as driver_dm from octavia_lib.api.drivers import data_models as driver_dm
from octavia_lib.common import constants as lib_consts
from oslo_config import cfg from oslo_config import cfg
from oslo_db import exception as odb_exceptions from oslo_db import exception as odb_exceptions
from oslo_log import log as logging from oslo_log import log as logging
@@ -164,16 +165,19 @@ class HealthMonitorController(base.BaseController):
# do not give any information as to what constraint failed # do not give any information as to what constraint failed
raise exceptions.InvalidOption(value='', option='') from e raise exceptions.InvalidOption(value='', option='') from e
def _validate_healthmonitor_request_for_udp(self, request): def _validate_healthmonitor_request_for_udp_sctp(self, request,
pool_protocol):
if request.type not in ( if request.type not in (
consts.HEALTH_MONITOR_UDP_CONNECT, consts.HEALTH_MONITOR_UDP_CONNECT,
lib_consts.HEALTH_MONITOR_SCTP,
consts.HEALTH_MONITOR_TCP, consts.HEALTH_MONITOR_TCP,
consts.HEALTH_MONITOR_HTTP): consts.HEALTH_MONITOR_HTTP):
raise exceptions.ValidationException(detail=_( raise exceptions.ValidationException(detail=_(
"The associated pool protocol is %(pool_protocol)s, so only " "The associated pool protocol is %(pool_protocol)s, so only "
"a %(types)s health monitor is supported.") % { "a %(types)s health monitor is supported.") % {
'pool_protocol': consts.PROTOCOL_UDP, 'pool_protocol': pool_protocol,
'types': '/'.join((consts.HEALTH_MONITOR_UDP_CONNECT, 'types': '/'.join((consts.HEALTH_MONITOR_UDP_CONNECT,
lib_consts.HEALTH_MONITOR_SCTP,
consts.HEALTH_MONITOR_TCP, consts.HEALTH_MONITOR_TCP,
consts.HEALTH_MONITOR_HTTP))}) consts.HEALTH_MONITOR_HTTP))})
# check the delay value if the HM type is UDP-CONNECT # check the delay value if the HM type is UDP-CONNECT
@@ -209,14 +213,19 @@ class HealthMonitorController(base.BaseController):
raise exceptions.DisabledOption( raise exceptions.DisabledOption(
option='type', value=consts.HEALTH_MONITOR_PING) option='type', value=consts.HEALTH_MONITOR_PING)
if pool.protocol == consts.PROTOCOL_UDP: if pool.protocol in (lib_consts.PROTOCOL_UDP,
self._validate_healthmonitor_request_for_udp(health_monitor) lib_consts.PROTOCOL_SCTP):
self._validate_healthmonitor_request_for_udp_sctp(health_monitor,
pool.protocol)
else: else:
if health_monitor.type == consts.HEALTH_MONITOR_UDP_CONNECT: if health_monitor.type in (consts.HEALTH_MONITOR_UDP_CONNECT,
lib_consts.HEALTH_MONITOR_SCTP):
raise exceptions.ValidationException(detail=_( raise exceptions.ValidationException(detail=_(
"The %(type)s type is only supported for pools of type " "The %(type)s type is only supported for pools of type "
"%(protocol)s.") % {'type': health_monitor.type, "%(protocols)s.") % {
'protocol': consts.PROTOCOL_UDP}) 'type': health_monitor.type,
'protocols': '/'.join((consts.PROTOCOL_UDP,
lib_consts.PROTOCOL_SCTP))})
# Load the driver early as it also provides validation # Load the driver early as it also provides validation
driver = driver_factory.get_driver(provider) driver = driver_factory.get_driver(provider)
@@ -342,11 +351,12 @@ class HealthMonitorController(base.BaseController):
self._auth_validate_action(context, project_id, consts.RBAC_PUT) self._auth_validate_action(context, project_id, consts.RBAC_PUT)
self._validate_update_hm(db_hm, health_monitor) self._validate_update_hm(db_hm, health_monitor)
# Validate health monitor update options for UDP-CONNECT type. # Validate health monitor update options for UDP/SCTP
if (pool.protocol == consts.PROTOCOL_UDP and if pool.protocol in (lib_consts.PROTOCOL_UDP,
db_hm.type == consts.HEALTH_MONITOR_UDP_CONNECT): lib_consts.PROTOCOL_SCTP):
health_monitor.type = db_hm.type health_monitor.type = db_hm.type
self._validate_healthmonitor_request_for_udp(health_monitor) self._validate_healthmonitor_request_for_udp_sctp(health_monitor,
pool.protocol)
self._set_default_on_none(health_monitor) self._set_default_on_none(health_monitor)

View File

@@ -14,6 +14,7 @@
# under the License. # under the License.
from octavia_lib.api.drivers import data_models as driver_dm from octavia_lib.api.drivers import data_models as driver_dm
from octavia_lib.common import constants as lib_consts
from oslo_config import cfg from oslo_config import cfg
from oslo_db import exception as odb_exceptions from oslo_db import exception as odb_exceptions
from oslo_log import log as logging from oslo_log import log as logging
@@ -175,12 +176,13 @@ class ListenersController(base.BaseController):
self._validate_insert_headers( self._validate_insert_headers(
listener_dict['insert_headers'].keys(), listener_protocol) listener_dict['insert_headers'].keys(), listener_protocol)
# Check for UDP compatibility # Check for UDP/SCTP compatibility
if (listener_protocol == constants.PROTOCOL_UDP and if (listener_protocol in (constants.PROTOCOL_UDP,
lib_consts.PROTOCOL_SCTP) and
self._is_tls_or_insert_header(listener_dict)): self._is_tls_or_insert_header(listener_dict)):
raise exceptions.ValidationException( raise exceptions.ValidationException(
detail=_("%s protocol listener does not " detail=_("%s protocol listener does not "
"support TLS.") % constants.PROTOCOL_UDP) "support TLS.") % listener_protocol)
# Check for TLS disabled # Check for TLS disabled
if (not CONF.api_settings.allow_tls_terminated_listeners and if (not CONF.api_settings.allow_tls_terminated_listeners and
@@ -251,8 +253,8 @@ class ListenersController(base.BaseController):
listener_dict.get('client_ca_tls_certificate_id'), listener_dict.get('client_ca_tls_certificate_id'),
listener_dict.get('client_crl_container_id', None)) listener_dict.get('client_crl_container_id', None))
# Validate that the L4 protocol (UDP or TCP) is not already used for # Validate that the L4 protocol (UDP, TCP or SCTP) is not already used
# the specified protocol_port in this load balancer # for the specified protocol_port in this load balancer
pcontext = pecan_request.context pcontext = pecan_request.context
query_filter = { query_filter = {
'project_id': listener_dict['project_id'], 'project_id': listener_dict['project_id'],
@@ -435,12 +437,13 @@ class ListenersController(base.BaseController):
raise exceptions.ValidationException( raise exceptions.ValidationException(
detail='No listener object supplied.') detail='No listener object supplied.')
# Check for UDP compatibility # Check for UDP/SCTP compatibility
if (db_listener.protocol == constants.PROTOCOL_UDP and if (db_listener.protocol in (constants.PROTOCOL_UDP,
lib_consts.PROTOCOL_SCTP) and
self._is_tls_or_insert_header(listener.to_dict())): self._is_tls_or_insert_header(listener.to_dict())):
raise exceptions.ValidationException(detail=_( raise exceptions.ValidationException(detail=_(
"%s protocol listener does not support TLS or header " "%s protocol listener does not support TLS or header "
"insertion.") % constants.PROTOCOL_UDP) "insertion.") % db_listener.protocol)
# Check for certs when not TERMINATED_HTTPS # Check for certs when not TERMINATED_HTTPS
if (db_listener.protocol != constants.PROTOCOL_TERMINATED_HTTPS and if (db_listener.protocol != constants.PROTOCOL_TERMINATED_HTTPS and

View File

@@ -14,6 +14,7 @@
# under the License. # under the License.
from octavia_lib.api.drivers import data_models as driver_dm from octavia_lib.api.drivers import data_models as driver_dm
from octavia_lib.common import constants as lib_consts
from oslo_config import cfg from oslo_config import cfg
from oslo_db import exception as odb_exceptions from oslo_db import exception as odb_exceptions
from oslo_log import log as logging from oslo_log import log as logging
@@ -165,7 +166,7 @@ class PoolsController(base.BaseController):
return False return False
return True return True
def _validate_pool_request_for_udp(self, request): def _validate_pool_request_for_udp_sctp(self, request):
if request.session_persistence: if request.session_persistence:
if (request.session_persistence.type == if (request.session_persistence.type ==
constants.SESSION_PERSISTENCE_SOURCE_IP and constants.SESSION_PERSISTENCE_SOURCE_IP and
@@ -174,14 +175,15 @@ class PoolsController(base.BaseController):
check_exist_attrs=['type', 'persistence_timeout', check_exist_attrs=['type', 'persistence_timeout',
'persistence_granularity'])): 'persistence_granularity'])):
raise exceptions.ValidationException(detail=_( raise exceptions.ValidationException(detail=_(
"session_persistence %s type for UDP protocol " "session_persistence %s type for UDP and SCTP protocols "
"only accepts: type, persistence_timeout, " "only accepts: type, persistence_timeout, "
"persistence_granularity.") % ( "persistence_granularity.") % (
constants.SESSION_PERSISTENCE_SOURCE_IP)) constants.SESSION_PERSISTENCE_SOURCE_IP))
if request.session_persistence.cookie_name: if request.session_persistence.cookie_name:
raise exceptions.ValidationException(detail=_( raise exceptions.ValidationException(detail=_(
"Cookie names are not supported for %s pools.") % "Cookie names are not supported for %s pools.") %
constants.PROTOCOL_UDP) "/".join((constants.PROTOCOL_UDP,
lib_consts.PROTOCOL_SCTP)))
if request.session_persistence.type in [ if request.session_persistence.type in [
constants.SESSION_PERSISTENCE_HTTP_COOKIE, constants.SESSION_PERSISTENCE_HTTP_COOKIE,
constants.SESSION_PERSISTENCE_APP_COOKIE]: constants.SESSION_PERSISTENCE_APP_COOKIE]:
@@ -189,7 +191,8 @@ class PoolsController(base.BaseController):
"Session persistence of type %(type)s is not supported " "Session persistence of type %(type)s is not supported "
"for %(protocol)s protocol pools.") % { "for %(protocol)s protocol pools.") % {
'type': request.session_persistence.type, 'type': request.session_persistence.type,
'protocol': constants.PROTOCOL_UDP}) 'protocol': "/".join((constants.PROTOCOL_UDP,
lib_consts.PROTOCOL_SCTP))})
@wsme_pecan.wsexpose(pool_types.PoolRootResponse, @wsme_pecan.wsexpose(pool_types.PoolRootResponse,
body=pool_types.PoolRootPOST, status_code=201) body=pool_types.PoolRootPOST, status_code=201)
@@ -226,15 +229,16 @@ class PoolsController(base.BaseController):
if pool.listener_id and listener: if pool.listener_id and listener:
self._validate_protocol(listener.protocol, pool.protocol) self._validate_protocol(listener.protocol, pool.protocol)
if pool.protocol == constants.PROTOCOL_UDP: if pool.protocol in (constants.PROTOCOL_UDP,
self._validate_pool_request_for_udp(pool) lib_consts.PROTOCOL_SCTP):
self._validate_pool_request_for_udp_sctp(pool)
else: else:
if (pool.session_persistence and ( if (pool.session_persistence and (
pool.session_persistence.persistence_timeout or pool.session_persistence.persistence_timeout or
pool.session_persistence.persistence_granularity)): pool.session_persistence.persistence_granularity)):
raise exceptions.ValidationException(detail=_( raise exceptions.ValidationException(detail=_(
"persistence_timeout and persistence_granularity " "persistence_timeout and persistence_granularity "
"is only for UDP protocol pools.")) "is only for UDP and SCTP protocol pools."))
if pool.session_persistence: if pool.session_persistence:
sp_dict = pool.session_persistence.to_dict(render_unsets=False) sp_dict = pool.session_persistence.to_dict(render_unsets=False)
@@ -311,16 +315,20 @@ class PoolsController(base.BaseController):
hm[constants.PROJECT_ID] = db_pool.project_id hm[constants.PROJECT_ID] = db_pool.project_id
new_hm = health_monitor.HealthMonitorController()._graph_create( new_hm = health_monitor.HealthMonitorController()._graph_create(
lock_session, hm) lock_session, hm)
if db_pool.protocol == constants.PROTOCOL_UDP: if db_pool.protocol in (constants.PROTOCOL_UDP,
lib_consts.PROTOCOL_SCTP):
health_monitor.HealthMonitorController( health_monitor.HealthMonitorController(
)._validate_healthmonitor_request_for_udp(new_hm) )._validate_healthmonitor_request_for_udp_sctp(new_hm,
db_pool)
else: else:
if new_hm.type == constants.HEALTH_MONITOR_UDP_CONNECT: if new_hm.type in (constants.HEALTH_MONITOR_UDP_CONNECT,
lib_consts.HEALTH_MONITOR_SCTP):
raise exceptions.ValidationException(detail=_( raise exceptions.ValidationException(detail=_(
"The %(type)s type is only supported for pools of " "The %(type)s type is only supported for pools of "
"type %(protocol)s.") % { "type %(protocol)s.") % {
'type': new_hm.type, 'type': new_hm.type,
'protocol': constants.PROTOCOL_UDP}) 'protocol': '/'.join((constants.PROTOCOL_UDP,
lib_consts.PROTOCOL_SCTP))})
db_pool.health_monitor = new_hm db_pool.health_monitor = new_hm
# Now check quotas for members # Now check quotas for members
@@ -344,8 +352,9 @@ class PoolsController(base.BaseController):
def _validate_pool_PUT(self, pool, db_pool): def _validate_pool_PUT(self, pool, db_pool):
if db_pool.protocol == constants.PROTOCOL_UDP: if db_pool.protocol in (constants.PROTOCOL_UDP,
self._validate_pool_request_for_udp(pool) lib_consts.PROTOCOL_SCTP):
self._validate_pool_request_for_udp_sctp(pool)
else: else:
if (pool.session_persistence and ( if (pool.session_persistence and (
pool.session_persistence.persistence_timeout or pool.session_persistence.persistence_timeout or

View File

@@ -214,7 +214,8 @@ VALID_LISTENER_POOL_PROTOCOL_MAP = {
lib_consts.PROTOCOL_PROXYV2, PROTOCOL_TCP], lib_consts.PROTOCOL_PROXYV2, PROTOCOL_TCP],
PROTOCOL_TERMINATED_HTTPS: [PROTOCOL_HTTP, PROTOCOL_PROXY, PROTOCOL_TERMINATED_HTTPS: [PROTOCOL_HTTP, PROTOCOL_PROXY,
lib_consts.PROTOCOL_PROXYV2], lib_consts.PROTOCOL_PROXYV2],
PROTOCOL_UDP: [PROTOCOL_UDP]} PROTOCOL_UDP: [PROTOCOL_UDP],
lib_consts.PROTOCOL_SCTP: [lib_consts.PROTOCOL_SCTP]}
# API Integer Ranges # API Integer Ranges
MIN_PORT_NUMBER = 1 MIN_PORT_NUMBER = 1
@@ -815,6 +816,7 @@ L4_PROTOCOL_MAP = {
PROTOCOL_PROXY: PROTOCOL_TCP, PROTOCOL_PROXY: PROTOCOL_TCP,
lib_consts.PROTOCOL_PROXYV2: PROTOCOL_TCP, lib_consts.PROTOCOL_PROXYV2: PROTOCOL_TCP,
PROTOCOL_UDP: PROTOCOL_UDP, PROTOCOL_UDP: PROTOCOL_UDP,
lib_consts.PROTOCOL_SCTP: lib_consts.PROTOCOL_SCTP,
} }
# Image drivers # Image drivers

View File

@@ -0,0 +1,46 @@
# Copyright 2020 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.
"""sctp support
Revision ID: 8b47b2546312
Revises: e6ee84f0abf3
Create Date: 2020-06-26 09:26:45.397873
"""
from alembic import op
import sqlalchemy as sa
from sqlalchemy import sql
# revision identifiers, used by Alembic.
revision = '8b47b2546312'
down_revision = 'e6ee84f0abf3'
def upgrade():
for table in ['protocol', 'health_monitor_type']:
insert_table = sql.table(
table,
sql.column(u'name', sa.String),
sql.column(u'description', sa.String)
)
op.bulk_insert(
insert_table,
[
{'name': 'SCTP'}
]
)

View File

@@ -16,6 +16,7 @@ import time
from neutronclient.common import exceptions as neutron_client_exceptions from neutronclient.common import exceptions as neutron_client_exceptions
from novaclient import exceptions as nova_client_exceptions from novaclient import exceptions as nova_client_exceptions
from octavia_lib.common import constants as lib_consts
from oslo_config import cfg from oslo_config import cfg
from oslo_log import log as logging from oslo_log import log as logging
from stevedore import driver as stevedore_driver from stevedore import driver as stevedore_driver
@@ -159,6 +160,8 @@ class AllowedAddressPairsDriver(neutron_base.BaseNeutronDriver):
protocol = constants.PROTOCOL_TCP.lower() protocol = constants.PROTOCOL_TCP.lower()
if listener.protocol == constants.PROTOCOL_UDP: if listener.protocol == constants.PROTOCOL_UDP:
protocol = constants.PROTOCOL_UDP.lower() protocol = constants.PROTOCOL_UDP.lower()
elif listener.protocol == lib_consts.PROTOCOL_SCTP:
protocol = lib_consts.PROTOCOL_SCTP.lower()
if listener.allowed_cidrs: if listener.allowed_cidrs:
for ac in listener.allowed_cidrs: for ac in listener.allowed_cidrs:
@@ -183,7 +186,8 @@ class AllowedAddressPairsDriver(neutron_base.BaseNeutronDriver):
# None ports with the egress rules. VRRP uses protocol 51 and 112 # None ports with the egress rules. VRRP uses protocol 51 and 112
if (rule.get('direction') == 'egress' or if (rule.get('direction') == 'egress' or
rule.get('protocol').upper() not in rule.get('protocol').upper() not in
[constants.PROTOCOL_TCP, constants.PROTOCOL_UDP]): [constants.PROTOCOL_TCP, constants.PROTOCOL_UDP,
lib_consts.PROTOCOL_SCTP]):
continue continue
old_ports.append((rule.get('port_range_max'), old_ports.append((rule.get('port_range_max'),
rule.get('protocol').lower(), rule.get('protocol').lower(),

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(23, len(version_ids)) self.assertEqual(24, 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)
@@ -69,6 +69,7 @@ class TestRootController(base_db_test.OctaviaDBTestBase):
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) self.assertIn('v2.22', version_ids)
self.assertIn('v2.23', 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

@@ -24,6 +24,7 @@ from octavia.common import data_models
from octavia.common import exceptions from octavia.common import exceptions
from octavia.db import repositories from octavia.db import repositories
from octavia.tests.functional.api.v2 import base from octavia.tests.functional.api.v2 import base
from octavia_lib.common import constants as lib_consts
class TestHealthMonitor(base.BaseAPITest): class TestHealthMonitor(base.BaseAPITest):
@@ -56,6 +57,7 @@ class TestHealthMonitor(base.BaseAPITest):
self.set_lb_status(self.lb_id) self.set_lb_status(self.lb_id)
self.pool_repo = repositories.PoolRepository() self.pool_repo = repositories.PoolRepository()
self._setup_udp_lb_resources() self._setup_udp_lb_resources()
self._setup_sctp_lb_resources()
def _setup_udp_lb_resources(self): def _setup_udp_lb_resources(self):
self.udp_lb = self.create_load_balancer(uuidutils.generate_uuid()).get( self.udp_lb = self.create_load_balancer(uuidutils.generate_uuid()).get(
@@ -80,6 +82,25 @@ class TestHealthMonitor(base.BaseAPITest):
group='api_settings', group='api_settings',
udp_connect_min_interval_health_monitor='3') udp_connect_min_interval_health_monitor='3')
def _setup_sctp_lb_resources(self):
self.sctp_lb = self.create_load_balancer(
uuidutils.generate_uuid()).get('loadbalancer')
self.sctp_lb_id = self.sctp_lb.get('id')
self.set_lb_status(self.sctp_lb_id)
self.sctp_listener = self.create_listener(
lib_consts.PROTOCOL_SCTP, 8888,
self.sctp_lb_id).get('listener')
self.sctp_listener_id = self.sctp_listener.get('id')
self.set_lb_status(self.sctp_lb_id)
self.sctp_pool_with_listener = self.create_pool(
None, lib_consts.PROTOCOL_SCTP, constants.LB_ALGORITHM_ROUND_ROBIN,
listener_id=self.sctp_listener_id)
self.sctp_pool_with_listener_id = (
self.sctp_pool_with_listener.get('pool').get('id'))
self.set_lb_status(self.sctp_lb_id)
def test_get(self): def test_get(self):
api_hm = self.create_health_monitor( api_hm = self.create_health_monitor(
self.pool_id, constants.HEALTH_MONITOR_HTTP, self.pool_id, constants.HEALTH_MONITOR_HTTP,
@@ -936,6 +957,32 @@ class TestHealthMonitor(base.BaseAPITest):
self.assertEqual('/test.html', api_hm.get('url_path')) self.assertEqual('/test.html', api_hm.get('url_path'))
self.assertEqual('200-201', api_hm.get('expected_codes')) self.assertEqual('200-201', api_hm.get('expected_codes'))
def test_create_udp_case_with_sctp_type(self):
# create with SCTP type
api_hm = self.create_health_monitor(
self.udp_pool_with_listener_id,
lib_consts.HEALTH_MONITOR_SCTP,
3, 1, 1, 1).get(self.root_tag)
self.assert_correct_status(
lb_id=self.udp_lb_id, listener_id=self.udp_listener_id,
pool_id=self.udp_pool_with_listener_id, hm_id=api_hm.get('id'),
lb_prov_status=constants.PENDING_UPDATE,
listener_prov_status=constants.PENDING_UPDATE,
pool_prov_status=constants.PENDING_UPDATE,
hm_prov_status=constants.PENDING_CREATE,
hm_op_status=constants.OFFLINE)
self.set_lb_status(self.udp_lb_id)
self.assertEqual(lib_consts.HEALTH_MONITOR_SCTP,
api_hm.get('type'))
self.assertEqual(3, api_hm.get('delay'))
self.assertEqual(1, api_hm.get('timeout'))
self.assertEqual(1, api_hm.get('max_retries_down'))
self.assertEqual(1, api_hm.get('max_retries'))
# Verify the L7 fields is None
self.assertIsNone(api_hm.get('http_method'))
self.assertIsNone(api_hm.get('url_path'))
self.assertIsNone(api_hm.get('expected_codes'))
def test_udp_case_when_udp_connect_min_interval_health_monitor_set(self): def test_udp_case_when_udp_connect_min_interval_health_monitor_set(self):
# negative case first # negative case first
req_dict = {'pool_id': self.udp_pool_with_listener_id, req_dict = {'pool_id': self.udp_pool_with_listener_id,
@@ -981,6 +1028,7 @@ class TestHealthMonitor(base.BaseAPITest):
"monitor is supported.") % { "monitor is supported.") % {
'pool_protocol': constants.PROTOCOL_UDP, 'pool_protocol': constants.PROTOCOL_UDP,
'types': '/'.join([constants.HEALTH_MONITOR_UDP_CONNECT, 'types': '/'.join([constants.HEALTH_MONITOR_UDP_CONNECT,
lib_consts.HEALTH_MONITOR_SCTP,
constants.HEALTH_MONITOR_TCP, constants.HEALTH_MONITOR_TCP,
constants.HEALTH_MONITOR_HTTP])} constants.HEALTH_MONITOR_HTTP])}
@@ -1005,7 +1053,8 @@ class TestHealthMonitor(base.BaseAPITest):
"supported for pools of type " "supported for pools of type "
"%(protocol)s.") % { "%(protocol)s.") % {
'type': constants.HEALTH_MONITOR_UDP_CONNECT, 'type': constants.HEALTH_MONITOR_UDP_CONNECT,
'protocol': constants.PROTOCOL_UDP} 'protocol': '/'.join((constants.PROTOCOL_UDP,
lib_consts.PROTOCOL_SCTP))}
res = self.post(self.HMS_PATH, self._build_body(req_dict), res = self.post(self.HMS_PATH, self._build_body(req_dict),
status=400, status=400,
expect_errors=True) expect_errors=True)
@@ -1014,6 +1063,108 @@ class TestHealthMonitor(base.BaseAPITest):
lb_id=self.udp_lb_id, listener_id=self.udp_listener_id, lb_id=self.udp_lb_id, listener_id=self.udp_listener_id,
pool_id=self.udp_pool_with_listener_id) pool_id=self.udp_pool_with_listener_id)
def test_create_sctp_case_with_udp_connect_type(self):
# create with UDP-CONNECT type
api_hm = self.create_health_monitor(
self.sctp_pool_with_listener_id,
constants.HEALTH_MONITOR_UDP_CONNECT,
3, 1, 1, 1).get(self.root_tag)
self.assert_correct_status(
lb_id=self.sctp_lb_id, listener_id=self.sctp_listener_id,
pool_id=self.sctp_pool_with_listener_id, hm_id=api_hm.get('id'),
lb_prov_status=constants.PENDING_UPDATE,
listener_prov_status=constants.PENDING_UPDATE,
pool_prov_status=constants.PENDING_UPDATE,
hm_prov_status=constants.PENDING_CREATE,
hm_op_status=constants.OFFLINE)
self.set_lb_status(self.sctp_lb_id)
self.assertEqual(constants.HEALTH_MONITOR_UDP_CONNECT,
api_hm.get('type'))
self.assertEqual(3, api_hm.get('delay'))
self.assertEqual(1, api_hm.get('timeout'))
self.assertEqual(1, api_hm.get('max_retries_down'))
self.assertEqual(1, api_hm.get('max_retries'))
# Verify the L7 fields is None
self.assertIsNone(api_hm.get('http_method'))
self.assertIsNone(api_hm.get('url_path'))
self.assertIsNone(api_hm.get('expected_codes'))
def test_create_sctp_case_with_tcp_type(self):
# create with TCP type
api_hm = self.create_health_monitor(
self.sctp_pool_with_listener_id, constants.HEALTH_MONITOR_TCP,
3, 1, 1, 1).get(self.root_tag)
self.assert_correct_status(
lb_id=self.sctp_lb_id, listener_id=self.sctp_listener_id,
pool_id=self.sctp_pool_with_listener_id, hm_id=api_hm.get('id'),
lb_prov_status=constants.PENDING_UPDATE,
listener_prov_status=constants.PENDING_UPDATE,
pool_prov_status=constants.PENDING_UPDATE,
hm_prov_status=constants.PENDING_CREATE,
hm_op_status=constants.OFFLINE)
self.set_lb_status(self.sctp_lb_id)
self.assertEqual(constants.HEALTH_MONITOR_TCP, api_hm.get('type'))
self.assertEqual(3, api_hm.get('delay'))
self.assertEqual(1, api_hm.get('timeout'))
self.assertEqual(1, api_hm.get('max_retries_down'))
self.assertEqual(1, api_hm.get('max_retries'))
self.assertIsNone(api_hm.get('http_method'))
self.assertIsNone(api_hm.get('url_path'))
self.assertIsNone(api_hm.get('expected_codes'))
def test_create_sctp_case_with_http_type(self):
# create with HTTP type
api_hm = self.create_health_monitor(
self.sctp_pool_with_listener_id, constants.HEALTH_MONITOR_HTTP,
3, 1, 1, 1, url_path='/test.html',
http_method=constants.HEALTH_MONITOR_HTTP_METHOD_GET,
expected_codes='200-201').get(self.root_tag)
self.assert_correct_status(
lb_id=self.sctp_lb_id, listener_id=self.sctp_listener_id,
pool_id=self.sctp_pool_with_listener_id, hm_id=api_hm.get('id'),
lb_prov_status=constants.PENDING_UPDATE,
listener_prov_status=constants.PENDING_UPDATE,
pool_prov_status=constants.PENDING_UPDATE,
hm_prov_status=constants.PENDING_CREATE,
hm_op_status=constants.OFFLINE)
self.set_lb_status(self.sctp_lb_id)
self.assertEqual(constants.HEALTH_MONITOR_HTTP, api_hm.get('type'))
self.assertEqual(3, api_hm.get('delay'))
self.assertEqual(1, api_hm.get('timeout'))
self.assertEqual(1, api_hm.get('max_retries_down'))
self.assertEqual(1, api_hm.get('max_retries'))
self.assertEqual(3, api_hm.get('delay'))
self.assertEqual(constants.HEALTH_MONITOR_HTTP_METHOD_GET,
api_hm.get('http_method'))
self.assertEqual('/test.html', api_hm.get('url_path'))
self.assertEqual('200-201', api_hm.get('expected_codes'))
def test_create_sctp_case_with_sctp_type(self):
# create with SCTP type
api_hm = self.create_health_monitor(
self.sctp_pool_with_listener_id,
lib_consts.HEALTH_MONITOR_SCTP,
3, 1, 1, 1).get(self.root_tag)
self.assert_correct_status(
lb_id=self.sctp_lb_id, listener_id=self.sctp_listener_id,
pool_id=self.sctp_pool_with_listener_id, hm_id=api_hm.get('id'),
lb_prov_status=constants.PENDING_UPDATE,
listener_prov_status=constants.PENDING_UPDATE,
pool_prov_status=constants.PENDING_UPDATE,
hm_prov_status=constants.PENDING_CREATE,
hm_op_status=constants.OFFLINE)
self.set_lb_status(self.sctp_lb_id)
self.assertEqual(lib_consts.HEALTH_MONITOR_SCTP,
api_hm.get('type'))
self.assertEqual(3, api_hm.get('delay'))
self.assertEqual(1, api_hm.get('timeout'))
self.assertEqual(1, api_hm.get('max_retries_down'))
self.assertEqual(1, api_hm.get('max_retries'))
# Verify the L7 fields is None
self.assertIsNone(api_hm.get('http_method'))
self.assertIsNone(api_hm.get('url_path'))
self.assertIsNone(api_hm.get('expected_codes'))
def test_ensure_L7_fields_filled_during_create(self): def test_ensure_L7_fields_filled_during_create(self):
# Create a health monitor with a load balancer pool # Create a health monitor with a load balancer pool
api_hm = self.create_health_monitor( api_hm = self.create_health_monitor(

View File

@@ -27,6 +27,7 @@ from octavia.db import api as db_api
from octavia.tests.common import constants as c_const from octavia.tests.common import constants as c_const
from octavia.tests.common import sample_certs from octavia.tests.common import sample_certs
from octavia.tests.functional.api.v2 import base from octavia.tests.functional.api.v2 import base
from octavia_lib.common import constants as lib_consts
class TestPool(base.BaseAPITest): class TestPool(base.BaseAPITest):
@@ -1026,7 +1027,9 @@ class TestPool(base.BaseAPITest):
'lb_algorithm': constants.LB_ALGORITHM_ROUND_ROBIN, 'lb_algorithm': constants.LB_ALGORITHM_ROUND_ROBIN,
'session_persistence': sp} 'session_persistence': sp}
expect_error_msg = ("Validation failure: Cookie names are not " expect_error_msg = ("Validation failure: Cookie names are not "
"supported for %s pools.") % constants.PROTOCOL_UDP "supported for %s pools.") % (
"/".join((constants.PROTOCOL_UDP,
lib_consts.PROTOCOL_SCTP)))
res = self.post(self.POOLS_PATH, self._build_body(req_dict), res = self.post(self.POOLS_PATH, self._build_body(req_dict),
status=400, expect_errors=True) status=400, expect_errors=True)
self.assertEqual(expect_error_msg, res.json['faultstring']) self.assertEqual(expect_error_msg, res.json['faultstring'])
@@ -1047,7 +1050,10 @@ class TestPool(base.BaseAPITest):
constants.SESSION_PERSISTENCE_APP_COOKIE]: constants.SESSION_PERSISTENCE_APP_COOKIE]:
expect_error_msg = ("Validation failure: Session persistence of " expect_error_msg = ("Validation failure: Session persistence of "
"type %s is not supported for %s protocol " "type %s is not supported for %s protocol "
"pools.") % (type, constants.PROTOCOL_UDP) "pools.") % (
type,
"/".join((constants.PROTOCOL_UDP,
lib_consts.PROTOCOL_SCTP)))
sp.update({'type': type}) sp.update({'type': type})
req_dict['session_persistence'] = sp req_dict['session_persistence'] = sp
res = self.post(self.POOLS_PATH, self._build_body(req_dict), res = self.post(self.POOLS_PATH, self._build_body(req_dict),
@@ -1070,9 +1076,11 @@ class TestPool(base.BaseAPITest):
'session_persistence': sp} 'session_persistence': sp}
expect_error_msg = ( expect_error_msg = (
"Validation failure: session_persistence %s type for %s " "Validation failure: session_persistence %s type for %s "
"protocol only accepts: type, persistence_timeout, " "protocols only accepts: type, persistence_timeout, "
"persistence_granularity.") % ( "persistence_granularity.") % (
constants.SESSION_PERSISTENCE_SOURCE_IP, constants.PROTOCOL_UDP) constants.SESSION_PERSISTENCE_SOURCE_IP,
" and ".join((constants.PROTOCOL_UDP,
lib_consts.PROTOCOL_SCTP)))
res = self.post(self.POOLS_PATH, self._build_body(req_dict), res = self.post(self.POOLS_PATH, self._build_body(req_dict),
status=400, expect_errors=True) status=400, expect_errors=True)
self.assertEqual(expect_error_msg, res.json['faultstring']) self.assertEqual(expect_error_msg, res.json['faultstring'])
@@ -1092,7 +1100,9 @@ class TestPool(base.BaseAPITest):
'lb_algorithm': constants.LB_ALGORITHM_ROUND_ROBIN} 'lb_algorithm': constants.LB_ALGORITHM_ROUND_ROBIN}
expect_error_msg = ("Validation failure: persistence_timeout and " expect_error_msg = ("Validation failure: persistence_timeout and "
"persistence_granularity is only for %s protocol " "persistence_granularity is only for %s protocol "
"pools.") % constants.PROTOCOL_UDP "pools.") % (
" and ".join((constants.PROTOCOL_UDP,
lib_consts.PROTOCOL_SCTP)))
for s in sps: for s in sps:
req_dict.update({'session_persistence': s}) req_dict.update({'session_persistence': s})
res = self.post(self.POOLS_PATH, self._build_body(req_dict), res = self.post(self.POOLS_PATH, self._build_body(req_dict),
@@ -1320,7 +1330,8 @@ class TestPool(base.BaseAPITest):
# Error during update pool with non-UDP type and cookie_name. # Error during update pool with non-UDP type and cookie_name.
expect_error_msg = ( expect_error_msg = (
"Validation failure: Cookie names are not supported for %s" "Validation failure: Cookie names are not supported for %s"
" pools.") % constants.PROTOCOL_UDP " pools.") % ("/".join((constants.PROTOCOL_UDP,
lib_consts.PROTOCOL_SCTP)))
sess_p['type'] = constants.SESSION_PERSISTENCE_HTTP_COOKIE sess_p['type'] = constants.SESSION_PERSISTENCE_HTTP_COOKIE
sess_p['cookie_name'] = 'test-cookie-name' sess_p['cookie_name'] = 'test-cookie-name'
new_pool = {'session_persistence': sess_p} new_pool = {'session_persistence': sess_p}
@@ -1333,10 +1344,11 @@ class TestPool(base.BaseAPITest):
# Error during update pool with source ip type and more options. # Error during update pool with source ip type and more options.
expect_error_msg = ( expect_error_msg = (
"Validation failure: session_persistence %s type for %s protocol " "Validation failure: session_persistence %s type for %s protocols "
"only accepts: type, persistence_timeout, " "only accepts: type, persistence_timeout, "
"persistence_granularity.") % ( "persistence_granularity.") % (
constants.SESSION_PERSISTENCE_SOURCE_IP, constants.PROTOCOL_UDP) constants.SESSION_PERSISTENCE_SOURCE_IP,
" and ".join((constants.PROTOCOL_UDP, lib_consts.PROTOCOL_SCTP)))
sess_p['type'] = constants.SESSION_PERSISTENCE_SOURCE_IP sess_p['type'] = constants.SESSION_PERSISTENCE_SOURCE_IP
sess_p['cookie_name'] = 'test-cookie-name' sess_p['cookie_name'] = 'test-cookie-name'
sess_p['persistence_timeout'] = 4 sess_p['persistence_timeout'] = 4
@@ -1354,7 +1366,10 @@ class TestPool(base.BaseAPITest):
constants.SESSION_PERSISTENCE_HTTP_COOKIE]: constants.SESSION_PERSISTENCE_HTTP_COOKIE]:
expect_error_msg = ("Validation failure: Session persistence of " expect_error_msg = ("Validation failure: Session persistence of "
"type %s is not supported for %s protocol " "type %s is not supported for %s protocol "
"pools.") % (ty, constants.PROTOCOL_UDP) "pools.") % (
ty,
"/".join((constants.PROTOCOL_UDP,
lib_consts.PROTOCOL_SCTP)))
sess_p['type'] = ty sess_p['type'] = ty
res = self.put(self.POOL_PATH.format(pool_id=api_pool.get('id')), res = self.put(self.POOL_PATH.format(pool_id=api_pool.get('id')),
self._build_body(new_pool), status=400, self._build_body(new_pool), status=400,

View File

@@ -0,0 +1,5 @@
---
features:
- |
Add support for SCTP protocol. SCTP support has been added in the Octavia
API for listener, pool, and health-monitor resources.