K8S Services: add support for SCTP

The Octavia API already supports SCTP, though the support for
Amphora driver and OVN driver is still in development. This change
ensures that Octavia loadbalancer has listeners and pools/members
created with SCTP protocol. Kuryr already handles the case where
listener's protocol is not supported by Octavia.

Partially-Implements: blueprint sctp-support
Change-Id: Ia320760807cdffacb91b45d858b90d79354ef962
This commit is contained in:
Tabitha Fasoyin 2021-01-04 11:42:37 +00:00
parent 500ff16481
commit c3e66123a5
4 changed files with 38 additions and 5 deletions

View File

@ -383,6 +383,12 @@ function configure_neutron_defaults {
--description "k8s service subnet UDP allowed" \ --description "k8s service subnet UDP allowed" \
--remote-ip "$service_cidr" --ethertype "$KURYR_ETHERTYPE" --protocol udp \ --remote-ip "$service_cidr" --ethertype "$KURYR_ETHERTYPE" --protocol udp \
"$service_pod_access_sg_id" "$service_pod_access_sg_id"
# Octavia supports SCTP load balancing, we need to also allow SCTP traffic
openstack --os-cloud devstack-admin --os-region "$REGION_NAME" \
security group rule create --project "$project_id" \
--description "k8s service subnet SCTP allowed" \
--remote-ip "$service_cidr" --ethertype "$KURYR_ETHERTYPE" --protocol sctp \
"$service_pod_access_sg_id"
if [[ "$KURYR_K8S_OCTAVIA_MEMBER_MODE" == "L3" ]]; then if [[ "$KURYR_K8S_OCTAVIA_MEMBER_MODE" == "L3" ]]; then
if [ -n "$sg_ids" ]; then if [ -n "$sg_ids" ]; then
@ -418,6 +424,12 @@ function configure_neutron_defaults {
--description "k8s pod subnet allowed from k8s-pod-subnet" \ --description "k8s pod subnet allowed from k8s-pod-subnet" \
--remote-ip "$pod_cidr" --ethertype "$KURYR_ETHERTYPE" --protocol udp \ --remote-ip "$pod_cidr" --ethertype "$KURYR_ETHERTYPE" --protocol udp \
"$octavia_pod_access_sg_id" "$octavia_pod_access_sg_id"
# Octavia supports SCTP load balancing, we need to also support SCTP traffic
openstack --os-cloud devstack-admin --os-region "$REGION_NAME" \
security group rule create --project "$project_id" \
--description "k8s pod subnet allowed from k8s-pod-subnet" \
--remote-ip "$pod_cidr" --ethertype "$KURYR_ETHERTYPE" --protocol sctp \
"$octavia_pod_access_sg_id"
if [ -n "$sg_ids" ]; then if [ -n "$sg_ids" ]; then
sg_ids+=",${octavia_pod_access_sg_id}" sg_ids+=",${octavia_pod_access_sg_id}"
else else

View File

@ -48,6 +48,7 @@ _OCTAVIA_TAGGING_VERSION = 2, 5
_OCTAVIA_DL_VERSION = 2, 13 _OCTAVIA_DL_VERSION = 2, 13
_OCTAVIA_ACL_VERSION = 2, 12 _OCTAVIA_ACL_VERSION = 2, 12
_OCTAVIA_PROVIDER_VERSION = 2, 6 _OCTAVIA_PROVIDER_VERSION = 2, 6
_OCTAVIA_SCTP_VERSION = 2, 23
# HTTP Codes raised by Octavia when a Resource already exists # HTTP Codes raised by Octavia when a Resource already exists
OKAY_CODES = (409, 500) OKAY_CODES = (409, 500)
@ -63,6 +64,7 @@ class LBaaSv2Driver(base.LBaaSDriver):
self._octavia_acls = False self._octavia_acls = False
self._octavia_double_listeners = False self._octavia_double_listeners = False
self._octavia_providers = False self._octavia_providers = False
self._octavia_sctp = False
# Check if Octavia API supports tagging. # Check if Octavia API supports tagging.
# TODO(dulek): *Maybe* this can be replaced with # TODO(dulek): *Maybe* this can be replaced with
# lbaas.get_api_major_version(version=_OCTAVIA_TAGGING_VERSION) # lbaas.get_api_major_version(version=_OCTAVIA_TAGGING_VERSION)
@ -79,6 +81,9 @@ class LBaaSv2Driver(base.LBaaSDriver):
if v >= _OCTAVIA_TAGGING_VERSION: if v >= _OCTAVIA_TAGGING_VERSION:
LOG.info('Octavia supports resource tags.') LOG.info('Octavia supports resource tags.')
self._octavia_tags = True self._octavia_tags = True
if v >= _OCTAVIA_SCTP_VERSION:
LOG.info('Octavia API supports SCTP protocol.')
self._octavia_sctp = True
else: else:
v_str = '%d.%d' % v v_str = '%d.%d' % v
LOG.warning('[neutron_defaults]resource_tags is set, but Octavia ' LOG.warning('[neutron_defaults]resource_tags is set, but Octavia '
@ -94,6 +99,9 @@ class LBaaSv2Driver(base.LBaaSDriver):
def providers_supported(self): def providers_supported(self):
return self._octavia_providers return self._octavia_providers
def sctp_supported(self):
return self._octavia_sctp
def get_octavia_version(self): def get_octavia_version(self):
lbaas = clients.get_loadbalancer_client() lbaas = clients.get_loadbalancer_client()
region_name = getattr(CONF.neutron, 'region_name', None) region_name = getattr(CONF.neutron, 'region_name', None)
@ -362,9 +370,8 @@ class LBaaSv2Driver(base.LBaaSDriver):
loadbalancer, listener, self._create_listener, loadbalancer, listener, self._create_listener,
self._find_listener, interval=_LB_STS_POLL_SLOW_INTERVAL) self._find_listener, interval=_LB_STS_POLL_SLOW_INTERVAL)
except os_exc.SDKException: except os_exc.SDKException:
LOG.exception("Listener creation failed, most probably because " LOG.exception("Failed when creating listener for loadbalancer "
"protocol %(prot)s is not supported", "%r", loadbalancer['id'])
{'prot': protocol})
return None return None
# NOTE(maysams): When ovn-octavia provider is used # NOTE(maysams): When ovn-octavia provider is used
@ -726,6 +733,14 @@ class LBaaSv2Driver(base.LBaaSDriver):
return result return result
except os_exc.BadRequestException: except os_exc.BadRequestException:
raise raise
except os_exc.HttpException as e:
if e.status_code == 501:
LOG.exception("Listener creation failed, most probably "
"because protocol %(prot)s is not supported",
{'prot': obj['protocol']})
return None
else:
raise
except os_exc.SDKException: except os_exc.SDKException:
pass pass

View File

@ -591,6 +591,10 @@ class KuryrLoadBalancerHandler(k8s_base.ResourceEventHandler):
LOG.warning("Skipping listener creation for %s as another one" LOG.warning("Skipping listener creation for %s as another one"
" already exists with port %s", name, port) " already exists with port %s", name, port)
continue continue
if protocol == "SCTP" and not self._drv_lbaas.sctp_supported():
LOG.warning("Skipping listener creation as provider does"
" not support %s protocol", protocol)
continue
listener = self._drv_lbaas.ensure_listener( listener = self._drv_lbaas.ensure_listener(
loadbalancer=loadbalancer_crd['status'].get('loadbalancer'), loadbalancer=loadbalancer_crd['status'].get('loadbalancer'),
protocol=protocol, protocol=protocol,

View File

@ -173,11 +173,13 @@ class TestUtils(test_base.TestCase):
def test_get_service_ports(self): def test_get_service_ports(self):
service = {'spec': {'ports': [ service = {'spec': {'ports': [
{'port': 1, 'targetPort': 1}, {'port': 1, 'targetPort': 1},
{'port': 2, 'name': 'X', 'protocol': 'UDP', 'targetPort': 2} {'port': 2, 'name': 'X', 'protocol': 'UDP', 'targetPort': 2},
{'port': 3, 'name': 'Y', 'protocol': 'SCTP', 'targetPort': 3}
]}} ]}}
expected_ret = [ expected_ret = [
{'port': 1, 'name': None, 'protocol': 'TCP', 'targetPort': '1'}, {'port': 1, 'name': None, 'protocol': 'TCP', 'targetPort': '1'},
{'port': 2, 'name': 'X', 'protocol': 'UDP', 'targetPort': '2'}] {'port': 2, 'name': 'X', 'protocol': 'UDP', 'targetPort': '2'},
{'port': 3, 'name': 'Y', 'protocol': 'SCTP', 'targetPort': '3'}]
ret = utils.get_service_ports(service) ret = utils.get_service_ports(service)
self.assertEqual(expected_ret, ret) self.assertEqual(expected_ret, ret)