From c3e66123a5e77b2d98b7adce51f02a9a262ab225 Mon Sep 17 00:00:00 2001 From: Tabitha Fasoyin Date: Mon, 4 Jan 2021 11:42:37 +0000 Subject: [PATCH] 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 --- devstack/plugin.sh | 12 +++++++++++ .../controller/drivers/lbaasv2.py | 21 ++++++++++++++++--- .../controller/handlers/loadbalancer.py | 4 ++++ kuryr_kubernetes/tests/unit/test_utils.py | 6 ++++-- 4 files changed, 38 insertions(+), 5 deletions(-) diff --git a/devstack/plugin.sh b/devstack/plugin.sh index de70f5f5f..f719c3170 100644 --- a/devstack/plugin.sh +++ b/devstack/plugin.sh @@ -383,6 +383,12 @@ function configure_neutron_defaults { --description "k8s service subnet UDP allowed" \ --remote-ip "$service_cidr" --ethertype "$KURYR_ETHERTYPE" --protocol udp \ "$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 [ -n "$sg_ids" ]; then @@ -418,6 +424,12 @@ function configure_neutron_defaults { --description "k8s pod subnet allowed from k8s-pod-subnet" \ --remote-ip "$pod_cidr" --ethertype "$KURYR_ETHERTYPE" --protocol udp \ "$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 sg_ids+=",${octavia_pod_access_sg_id}" else diff --git a/kuryr_kubernetes/controller/drivers/lbaasv2.py b/kuryr_kubernetes/controller/drivers/lbaasv2.py index 2c53ab03b..5dd7b972f 100644 --- a/kuryr_kubernetes/controller/drivers/lbaasv2.py +++ b/kuryr_kubernetes/controller/drivers/lbaasv2.py @@ -48,6 +48,7 @@ _OCTAVIA_TAGGING_VERSION = 2, 5 _OCTAVIA_DL_VERSION = 2, 13 _OCTAVIA_ACL_VERSION = 2, 12 _OCTAVIA_PROVIDER_VERSION = 2, 6 +_OCTAVIA_SCTP_VERSION = 2, 23 # HTTP Codes raised by Octavia when a Resource already exists OKAY_CODES = (409, 500) @@ -63,6 +64,7 @@ class LBaaSv2Driver(base.LBaaSDriver): self._octavia_acls = False self._octavia_double_listeners = False self._octavia_providers = False + self._octavia_sctp = False # Check if Octavia API supports tagging. # TODO(dulek): *Maybe* this can be replaced with # lbaas.get_api_major_version(version=_OCTAVIA_TAGGING_VERSION) @@ -79,6 +81,9 @@ class LBaaSv2Driver(base.LBaaSDriver): if v >= _OCTAVIA_TAGGING_VERSION: LOG.info('Octavia supports resource tags.') self._octavia_tags = True + if v >= _OCTAVIA_SCTP_VERSION: + LOG.info('Octavia API supports SCTP protocol.') + self._octavia_sctp = True else: v_str = '%d.%d' % v LOG.warning('[neutron_defaults]resource_tags is set, but Octavia ' @@ -94,6 +99,9 @@ class LBaaSv2Driver(base.LBaaSDriver): def providers_supported(self): return self._octavia_providers + def sctp_supported(self): + return self._octavia_sctp + def get_octavia_version(self): lbaas = clients.get_loadbalancer_client() region_name = getattr(CONF.neutron, 'region_name', None) @@ -362,9 +370,8 @@ class LBaaSv2Driver(base.LBaaSDriver): loadbalancer, listener, self._create_listener, self._find_listener, interval=_LB_STS_POLL_SLOW_INTERVAL) except os_exc.SDKException: - LOG.exception("Listener creation failed, most probably because " - "protocol %(prot)s is not supported", - {'prot': protocol}) + LOG.exception("Failed when creating listener for loadbalancer " + "%r", loadbalancer['id']) return None # NOTE(maysams): When ovn-octavia provider is used @@ -726,6 +733,14 @@ class LBaaSv2Driver(base.LBaaSDriver): return result except os_exc.BadRequestException: 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: pass diff --git a/kuryr_kubernetes/controller/handlers/loadbalancer.py b/kuryr_kubernetes/controller/handlers/loadbalancer.py index d9578652b..66244ab61 100644 --- a/kuryr_kubernetes/controller/handlers/loadbalancer.py +++ b/kuryr_kubernetes/controller/handlers/loadbalancer.py @@ -591,6 +591,10 @@ class KuryrLoadBalancerHandler(k8s_base.ResourceEventHandler): LOG.warning("Skipping listener creation for %s as another one" " already exists with port %s", name, port) 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( loadbalancer=loadbalancer_crd['status'].get('loadbalancer'), protocol=protocol, diff --git a/kuryr_kubernetes/tests/unit/test_utils.py b/kuryr_kubernetes/tests/unit/test_utils.py index 187dc93d9..4c6c88135 100644 --- a/kuryr_kubernetes/tests/unit/test_utils.py +++ b/kuryr_kubernetes/tests/unit/test_utils.py @@ -173,11 +173,13 @@ class TestUtils(test_base.TestCase): def test_get_service_ports(self): service = {'spec': {'ports': [ {'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 = [ {'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) self.assertEqual(expected_ret, ret)