From 4b5b14368861b9ca468d79326fabcd2992afb3d0 Mon Sep 17 00:00:00 2001 From: JerzySzachniewicz Date: Wed, 9 Dec 2020 10:11:07 +0100 Subject: Add option to use services with ipv6 Issue-ID: DCAEGEN2-2388 Signed-off-by: JerzySzachniewicz Change-Id: I34ca694b38e9c3121ec7a0bd95c92071392052d3 Signed-off-by: JerzySzachniewicz --- k8s/ChangeLog.md | 4 +- k8s/README.md | 11 ++++ k8s/k8sclient/k8sclient.py | 58 ++++++++++++++++------ k8s/k8splugin_types.yaml | 2 +- .../blueprints/nginx-svc-nodeport-ipv6.yaml | 25 ++++++++++ k8s/pom.xml | 2 +- k8s/setup.py | 2 +- k8s/tests/test_k8sclient.py | 44 +++++++++------- k8s/tests/test_tasks.py | 8 +-- 9 files changed, 114 insertions(+), 42 deletions(-) create mode 100644 k8s/plugin_testing_blueprints/blueprints/nginx-svc-nodeport-ipv6.yaml diff --git a/k8s/ChangeLog.md b/k8s/ChangeLog.md index ac7b0d5..908be27 100644 --- a/k8s/ChangeLog.md +++ b/k8s/ChangeLog.md @@ -4,7 +4,9 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/) and this project adheres to [Semantic Versioning](http://semver.org/). - +## [3.5.1] +* DCAEGEN2-2388 - Extend DCAE CFY K8S plugin to support IPv6 services. +* Add properties to ports list to support IPv6 services ## [3.5.0] * DCAEGEN2-2388 - Extend DCAE CFY K8S plugin to support IPv6 services. * Update kubernetes python plugin to version 12.0.1 diff --git a/k8s/README.md b/k8s/README.md index e310dee..db97040 100644 --- a/k8s/README.md +++ b/k8s/README.md @@ -136,6 +136,17 @@ setting the `` to 0 will expose the `` to other compo ports on the Kubernetes host's external interface. Setting `` to a non-zero value will expose that port on the external interfaces of every Kubernetes host in the cluster. (This uses the Kubernetes `NodePort` service type.) +In dualstack Kubernetes environment, adding parameter ipv6 or ipv4, specify which ip family will be used. +If ipv6 will be set in only ipv4 Kubernetes cluster, service will use ipv4 instead of declared ipv6. + +```yaml +ports: + - concat: ['8000:31000'] + ipv6: false + - concat: ['8000:31001'] + ipv6: true +``` + #### `max_wait` Integer - seconds to wait for component to become ready before throwing a `NonRecoverableError`. For example: diff --git a/k8s/k8sclient/k8sclient.py b/k8s/k8sclient/k8sclient.py index cd17999..401871f 100644 --- a/k8s/k8sclient/k8sclient.py +++ b/k8s/k8sclient/k8sclient.py @@ -59,6 +59,9 @@ def _create_service_name(component_name): def _create_exposed_service_name(component_name): return ("x{0}".format(component_name))[:63] +def _create_exposed_v6_service_name(component_name): + return ("x{0}-ipv6".format(component_name))[:63] + def _configure_api(location=None): # Look for a kubernetes config file if os.path.exists(K8S_CONFIG_PATH): @@ -223,11 +226,13 @@ def _create_deployment_object(component_name, return deployment -def _create_service_object(service_name, component_name, service_ports, annotations, labels, service_type): + +def _create_service_object(service_name, component_name, service_ports, annotations, labels, service_type, ip_family): service_spec = client.V1ServiceSpec( ports=service_ports, - selector={"app" : component_name}, - type=service_type + selector={"app": component_name}, + type=service_type, + ip_family=ip_family ) if annotations: metadata = client.V1ObjectMeta(name=_create_service_name(service_name), labels=labels, annotations=annotations) @@ -250,21 +255,28 @@ def parse_ports(port_list): container_ports = [] port_map = {} for p in port_list: + ipv6 = False + if type(p) is dict: + ipv6 = "ipv6" in p and p['ipv6'] + p = "".join(str(v) for v in p['concat']) m = PORTS.match(p.strip()) if m: cport = int(m.group(1)) - hport = int (m.group(4)) + hport = int(m.group(4)) if m.group(3): proto = (m.group(3)).upper() else: proto = "TCP" - container_ports.append((cport, proto)) - port_map[(cport, proto)] = hport + port = (cport, proto) + if port not in container_ports: + container_ports.append(port) + port_map[(cport, proto, ipv6)] = hport else: raise ValueError("Bad port specification: {0}".format(p)) return container_ports, port_map + def _parse_volumes(volume_list): volumes = [] volume_mounts = [] @@ -427,17 +439,24 @@ def _get_keystore_destination_paths(output_type, tls_cert_dir): }[output_type] return destination_paths_template.format(tls_cert_dir) + def _process_port_map(port_map): - service_ports = [] # Ports exposed internally on the k8s network - exposed_ports = [] # Ports to be mapped to ports on the k8s nodes via NodePort - for (cport, proto), hport in port_map.items(): + service_ports = [] # Ports exposed internally on the k8s network + exposed_ports = [] # Ports to be mapped to ports on the k8s nodes via NodePort + exposed_ports_ipv6 = [] + for (cport, proto, ipv6), hport in port_map.items(): name = "xport-{0}-{1}".format(proto[0].lower(), cport) cport = int(cport) hport = int(hport) - service_ports.append(client.V1ServicePort(port=cport, protocol=proto, name=name[1:])) + port = client.V1ServicePort(port=cport, protocol=proto, name=name[1:]) + if port not in service_ports: + service_ports.append(port) if hport != 0: - exposed_ports.append(client.V1ServicePort(port=cport, protocol=proto, node_port=hport, name=name)) - return service_ports, exposed_ports + if ipv6: + exposed_ports_ipv6.append(client.V1ServicePort(port=cport, protocol=proto, node_port=hport, name=name)) + else: + exposed_ports.append(client.V1ServicePort(port=cport, protocol=proto, node_port=hport, name=name)) + return service_ports, exposed_ports, exposed_ports_ipv6 def _service_exists(location, namespace, component_name): exists = False @@ -644,10 +663,11 @@ def deploy(ctx, namespace, component_name, image, replicas, always_pull, k8sconf # Create service(s), if a port mapping is specified if port_map: - service_ports, exposed_ports = _process_port_map(port_map) + service_ports, exposed_ports, exposed_ports_ipv6 = _process_port_map(port_map) # Create a ClusterIP service for access via the k8s network - service = _create_service_object(_create_service_name(component_name), component_name, service_ports, None, labels, "ClusterIP") + service = _create_service_object(_create_service_name(component_name), component_name, service_ports, None, + labels, "ClusterIP", "IPv4") core.create_namespaced_service(namespace, service) cip_service_created = True deployment_description["services"].append(_create_service_name(component_name)) @@ -655,10 +675,18 @@ def deploy(ctx, namespace, component_name, image, replicas, always_pull, k8sconf # If there are ports to be exposed on the k8s nodes, create a "NodePort" service if exposed_ports: exposed_service = \ - _create_service_object(_create_exposed_service_name(component_name), component_name, exposed_ports, '', labels, "NodePort") + _create_service_object(_create_exposed_service_name(component_name), component_name, exposed_ports, + '', labels, "NodePort", "IPv4") core.create_namespaced_service(namespace, exposed_service) deployment_description["services"].append(_create_exposed_service_name(component_name)) + if exposed_ports_ipv6: + exposed_service_ipv6 = \ + _create_service_object(_create_exposed_v6_service_name(component_name), component_name, + exposed_ports_ipv6, '', labels, "NodePort", "IPv6") + core.create_namespaced_service(namespace, exposed_service_ipv6) + deployment_description["services"].append(_create_exposed_v6_service_name(component_name)) + except Exception as e: # If the ClusterIP service was created, delete the service: if cip_service_created: diff --git a/k8s/k8splugin_types.yaml b/k8s/k8splugin_types.yaml index 1feb275..666d0ba 100644 --- a/k8s/k8splugin_types.yaml +++ b/k8s/k8splugin_types.yaml @@ -24,7 +24,7 @@ plugins: k8s: executor: 'central_deployment_agent' package_name: k8splugin - package_version: 3.5.0 + package_version: 3.5.1 data_types: diff --git a/k8s/plugin_testing_blueprints/blueprints/nginx-svc-nodeport-ipv6.yaml b/k8s/plugin_testing_blueprints/blueprints/nginx-svc-nodeport-ipv6.yaml new file mode 100644 index 0000000..a356daf --- /dev/null +++ b/k8s/plugin_testing_blueprints/blueprints/nginx-svc-nodeport-ipv6.yaml @@ -0,0 +1,25 @@ +tosca_definitions_version: cloudify_dsl_1_3 + +description: > + Simple blueprint to launch nginx as a "service component" + Exposes port as a NodePort service with ipv6 + If kubernetes doesn't have ipv6, service will get ipv4 + +imports: + - https://www.getcloudify.org/spec/cloudify/4.5.5/types.yaml + - plugin:k8splugin?version=>=3.0.0,<4.0.0 + +node_templates: + web_server: + type: dcae.nodes.ContainerizedServiceComponent + properties: + service_component_type: 'nginx-web' + image: nginx + docker_config: + healthcheck: + type: "http" + endpoint: "/" + ports: + - '80:0' + - concat: ['80:30581'] + ipv6: true diff --git a/k8s/pom.xml b/k8s/pom.xml index 0b77016..80e84b7 100644 --- a/k8s/pom.xml +++ b/k8s/pom.xml @@ -29,7 +29,7 @@ limitations under the License. org.onap.dcaegen2.platform.plugins k8s k8s-plugin - 3.5.0-SNAPSHOT + 3.5.1-SNAPSHOT http://maven.apache.org UTF-8 diff --git a/k8s/setup.py b/k8s/setup.py index e708ab1..9d813f2 100644 --- a/k8s/setup.py +++ b/k8s/setup.py @@ -24,7 +24,7 @@ from setuptools import setup setup( name='k8splugin', description='Cloudify plugin for containerized components deployed using Kubernetes', - version="3.5.0", + version="3.5.1", author='J. F. Lucas, Michael Hwang, Tommy Carpenter, Joanna Jeremicz, Sylwia Jakubek, Jan Malkiewicz, Remigiusz Janeczek, Piotr Marcinkiewicz', packages=['k8splugin','k8sclient','configure'], zip_safe=False, diff --git a/k8s/tests/test_k8sclient.py b/k8s/tests/test_k8sclient.py index 4f669d8..9e5bcb9 100644 --- a/k8s/tests/test_k8sclient.py +++ b/k8s/tests/test_k8sclient.py @@ -93,16 +93,18 @@ def test_parse_ports(): good_ports = [{"in": input, "ex": expected} for (input, expected) in [ - ("9101:0", (9101, 0, "TCP")), - ("9101/TCP:0", (9101, 0, "TCP")), - ("9101/tcp:0", (9101, 0, "TCP")), - ("9101/UDP:0", (9101, 0, "UDP")), - ("9101/udp:0", (9101, 0, "UDP")), - ("9101:31043", (9101, 31043, "TCP")), - ("9101/TCP:31043", (9101, 31043, "TCP")), - ("9101/tcp:31043", (9101, 31043, "TCP")), - ("9101/UDP:31043", (9101, 31043, "UDP")), - ("9101/udp:31043", (9101, 31043, "UDP")) + ({u'concat': [u'9101', u':', 0], u'ipv6': True}, (9101, 0, True, "TCP")), + ({u'concat': [u'9102', u':', 0], u'ipv6': False}, (9102, 0, False, "TCP")), + ({u'concat': [u'9103', u':', 36000], u'ipv6': True}, (9103, 36000, True, "TCP")), + ("9101/TCP:0", (9101, 0, False, "TCP")), + ("9101/tcp:0", (9101, 0, False, "TCP")), + ("9101/UDP:0", (9101, 0, False, "UDP")), + ("9101/udp:0", (9101, 0, False, "UDP")), + ("9101:31043", (9101, 31043, False, "TCP")), + ("9101/TCP:31043", (9101, 31043, False, "TCP")), + ("9101/tcp:31043", (9101, 31043, False, "TCP")), + ("9101/UDP:31043", (9101, 31043, False, "UDP")), + ("9101/udp:31043", (9101, 31043, False, "UDP")) ] ] @@ -118,7 +120,9 @@ def test_parse_ports(): ] port_list = [ - "9101:0", + {u'concat': [u'9101', u':', 3023], u'ipv6': True}, + {u'concat': [u'9102', u':', 0], u'ipv6': True}, + {u'concat': [u'9103', u':', 0], u'ipv6': False}, "5053/tcp:5053", "5053/udp:5053", "9661:19661", @@ -127,19 +131,21 @@ def test_parse_ports(): ] expected_port_map = { - (9101,"TCP") : 0, - (5053,"TCP") : 5053, - (5053,"UDP") : 5053, - (9661,"TCP") : 19661, - (9661,"UDP") : 19661, - (8080,"TCP") : 8080 + (9101, "TCP", True): 3023, + (9102, "TCP", True): 0, + (9103, "TCP", False): 0, + (5053, "TCP", False): 5053, + (5053, "UDP", False): 5053, + (9661, "TCP", False): 19661, + (9661, "UDP", False): 19661, + (8080, "TCP", False): 8080 } for test_case in good_ports: container_ports, port_map = parse_ports([test_case["in"]]) - (cport, hport, proto) = test_case["ex"] + (cport, hport, ipv6, proto) = test_case["ex"] assert container_ports == [(cport, proto)] - assert port_map == {(cport, proto) : hport} + assert port_map == {(cport, proto, ipv6): hport} for port in bad_ports: with pytest.raises(ValueError): diff --git a/k8s/tests/test_tasks.py b/k8s/tests/test_tasks.py index b82a4ae..50a3325 100644 --- a/k8s/tests/test_tasks.py +++ b/k8s/tests/test_tasks.py @@ -149,15 +149,15 @@ def test_enhance_docker_params(mockconfig): # Good - Test just docker config ports and volumes - test_kwargs = { "docker_config": { "ports": ["1:1", "2:2"], + test_kwargs = { "docker_config": { "ports": [{u'concat': [u'8080', u':', 0], u'ipv6': True}, "1:1"], "volumes": [{"container": "somewhere", "host": "somewhere else"}] }, "service_id": None } actual = tasks._enhance_docker_params(**test_kwargs) - assert actual == {'envs': {}, 'docker_config': {'ports': ['1:1', '2:2'], + assert actual == {'envs': {}, 'docker_config': {'ports': [{u'concat': [u'8080', u':', 0], u'ipv6': True}, "1:1"], 'volumes': [{'host': 'somewhere else', 'container': 'somewhere'}]}, - 'ports': ['1:1', '2:2'], 'volumes': [{'host': 'somewhere else', - 'container': 'somewhere'}], "service_id": None} + 'ports': [{u'concat': [u'8080', u':', 0], u'ipv6': True}, "1:1"], 'volumes': [{'host': 'somewhere else', + 'container': 'somewhere'}], "service_id": None} # Good - Test just docker config ports and volumes with overrrides -- cgit 1.2.3-korg