Add option to use services with ipv6 43/116243/3
authorJerzySzachniewicz <jerzy.szachniewicz@nokia.com>
Wed, 9 Dec 2020 09:11:07 +0000 (10:11 +0100)
committerJerzy Szachniewicz <jerzy.szachniewicz@nokia.com>
Thu, 10 Dec 2020 09:44:35 +0000 (09:44 +0000)
Issue-ID: DCAEGEN2-2388
Signed-off-by: JerzySzachniewicz <jerzy.szachniewicz@nokia.com>
Change-Id: I34ca694b38e9c3121ec7a0bd95c92071392052d3
Signed-off-by: JerzySzachniewicz <jerzy.szachniewicz@nokia.com>
k8s/ChangeLog.md
k8s/README.md
k8s/k8sclient/k8sclient.py
k8s/k8splugin_types.yaml
k8s/plugin_testing_blueprints/blueprints/nginx-svc-nodeport-ipv6.yaml [new file with mode: 0644]
k8s/pom.xml
k8s/setup.py
k8s/tests/test_k8sclient.py
k8s/tests/test_tasks.py

index ac7b0d5..908be27 100644 (file)
@@ -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 
index e310dee..db97040 100644 (file)
@@ -136,6 +136,17 @@ setting the `<host port>` to 0 will expose the `<container port>` to other compo
 ports on the Kubernetes host's external interface.    Setting `<host port>` 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:
index cd17999..401871f 100644 (file)
@@ -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:
index 1feb275..666d0ba 100644 (file)
@@ -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 (file)
index 0000000..a356daf
--- /dev/null
@@ -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
index 0b77016..80e84b7 100644 (file)
@@ -29,7 +29,7 @@ limitations under the License.
   <groupId>org.onap.dcaegen2.platform.plugins</groupId>
   <artifactId>k8s</artifactId>
   <name>k8s-plugin</name>
-  <version>3.5.0-SNAPSHOT</version>
+  <version>3.5.1-SNAPSHOT</version>
   <url>http://maven.apache.org</url>
   <properties>
     <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
index e708ab1..9d813f2 100644 (file)
@@ -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,
index 4f669d8..9e5bcb9 100644 (file)
@@ -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):
index b82a4ae..50a3325 100644 (file)
@@ -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