Add support for UDP port mapping 79/70379/1
authorJack Lucas <jflucas@att.com>
Fri, 12 Oct 2018 15:30:05 +0000 (11:30 -0400)
committerJack Lucas <jflucas@research.att.com>
Fri, 12 Oct 2018 18:34:25 +0000 (14:34 -0400)
Issue-ID: DCAEGEN2-796
Change-Id: I09038d0bc7154b989f9ce9e328da57aac0020597
Signed-off-by: Jack Lucas <jflucas@research.att.com>
.gitignore
k8s/README.md
k8s/k8sclient/k8sclient.py
k8s/pom.xml
k8s/setup.py
k8s/tests/test_k8sclient.py

index d11997c..22ce2d9 100644 (file)
@@ -6,6 +6,7 @@
 .project
 .pydevproject
 venv
+.vscode/
 
 
 # Byte-compiled / optimized / DLL files
index 2f4b3a6..dfe9937 100644 (file)
@@ -108,7 +108,9 @@ mode | Readable, writeable: `ro`, `rw`
 
 #### `ports`
 
-List of strings - Used to bind container ports to host ports. Each item is of the format: `<container port>:<host port>`.
+List of strings - Used to bind container ports to host ports. Each item is of the format: `<container port>:<host port>` or 
+<container port>/<protocol>:<host port>, where <protocol> can be "TCP", "tcp", "UDP", or "udp".   If the first format is used, the
+protocol defaults to TCP.
 
 Note that `ContainerizedPlatformComponent` has the property pair `host_port` and `container_port`. This pair will be merged with the input parameters ports.
 
index 4040642..806b41e 100644 (file)
@@ -32,6 +32,13 @@ INTERVAL_SPEC = re.compile("^([0-9]+)(s|m|h)?$")
 # Conversion factors to seconds
 FACTORS = {None: 1, "s": 1, "m": 60, "h": 3600}
 
+# Regular expression for port mapping
+# group 1: container port
+# group 2: / + protocol
+# group 3: protocol
+# group 4: host port
+PORTS = re.compile("^([0-9]+)(/(udp|UDP|tcp|TCP))?:([0-9]+)$")
+   
 def _create_deployment_name(component_name):
     return "dep-{0}".format(component_name)
 
@@ -129,7 +136,7 @@ def _create_container_object(name, image, always_pull, use_tls=False, env={}, co
     if readiness:
         hc_port = None
         if len(container_ports) > 0:
-            hc_port = container_ports[0]
+            (hc_port, proto) = container_ports[0]
         probe = _create_probe(readiness, hc_port, use_tls)
 
     # Define container for pod
@@ -138,7 +145,7 @@ def _create_container_object(name, image, always_pull, use_tls=False, env={}, co
         image=image,
         image_pull_policy='Always' if always_pull else 'IfNotPresent',
         env=env_vars,
-        ports=[client.V1ContainerPort(container_port=p) for p in container_ports],
+        ports=[client.V1ContainerPort(container_port=p, protocol=proto) for (p, proto) in container_ports],
         volume_mounts = volume_mounts,
         readiness_probe = probe
     )
@@ -207,17 +214,25 @@ def _create_service_object(service_name, component_name, service_ports, annotati
     return service
 
 def _parse_ports(port_list):
+    '''
+    Parse the port list into a list of container ports (needed to create the container)
+    and to a set of port mappings to set up k8s services.
+    '''
     container_ports = []
     port_map = {}
     for p in port_list:
-        try:
-            [container, host] = (p.strip()).split(":",2)
-            cport = int(container)
-            container_ports.append(cport)
-            hport = int(host)
-            port_map[container] = hport
-        except:
-            pass    # if something doesn't parse, we just ignore it
+        m = PORTS.match(p.strip())
+        if m:
+            cport = int(m.group(1))
+            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
+        else:
+            raise ValueError("Bad port specification: {0}".format(p))
 
     return container_ports, port_map
 
@@ -374,7 +389,7 @@ def deploy(namespace, component_name, image, replicas, always_pull, k8sconfig, *
         core = client.CoreV1Api()
         ext = client.ExtensionsV1beta1Api()
 
-        # Parse the port mapping into [container_port,...] and [{"host_port" : "container_port"},...]
+        # Parse the port mapping
         container_ports, port_map = _parse_ports(kwargs.get("ports", []))
 
         # Parse the volumes list into volumes and volume_mounts for the deployment
@@ -446,10 +461,10 @@ def deploy(namespace, component_name, image, replicas, always_pull, k8sconfig, *
         if 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, hport in port_map.iteritems():
-                service_ports.append(client.V1ServicePort(port=int(cport),name="port-{}".format(cport)))
+            for (cport, proto), hport in port_map.iteritems():
+                service_ports.append(client.V1ServicePort(port=int(cport),protocol=proto,name="port-{0}-{1}".format(proto[0].lower(), cport)))
                 if int(hport) != 0:
-                    exposed_ports.append(client.V1ServicePort(port=int(cport), node_port=int(hport),name="xport-{}".format(cport)))
+                    exposed_ports.append(client.V1ServicePort(port=int(cport),protocol=proto,node_port=int(hport),name="xport-{0}-{1}".format(proto[0].lower(),cport)))
 
             # If there are ports to be exposed via MSB, set up the annotation for the service
             msb_list = kwargs.get("msb_list")
index ea34f78..39649ca 100644 (file)
@@ -28,7 +28,7 @@ ECOMP is a trademark and service mark of AT&T Intellectual Property.
   <groupId>org.onap.dcaegen2.platform.plugins</groupId>
   <artifactId>k8s</artifactId>
   <name>k8s-plugin</name>
-  <version>1.4.3-SNAPSHOT</version>
+  <version>1.4.4-SNAPSHOT</version>
   <url>http://maven.apache.org</url>
   <properties>
     <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
index 5897f61..458328d 100644 (file)
@@ -23,7 +23,7 @@ from setuptools import setup
 setup(
     name='k8splugin',
     description='Cloudify plugin for containerized components deployed using Kubernetes',
-    version="1.4.3",
+    version="1.4.4",
     author='J. F. Lucas, Michael Hwang, Tommy Carpenter',
     packages=['k8splugin','k8sclient','msb','configure'],
     zip_safe=False,
index 00ccfdb..e985def 100644 (file)
@@ -83,4 +83,73 @@ def test_parse_interval():
 
     for interval in bad_intervals:
         with pytest.raises(ValueError):
-            _parse_interval(interval)
\ No newline at end of file
+            _parse_interval(interval)
+
+def test_parse_ports():
+    from k8sclient.k8sclient import _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"))
+        ]
+    ]
+    
+    bad_ports = [
+        "9101",
+        "9101:",
+        "9101:0x453",
+        "9101:0/udp",
+        "9101/:0",
+        "9101/u:0",
+        "9101/http:404",
+        "9101:-1"
+    ]
+
+    port_list = [
+        "9101:0",
+        "5053/tcp:5053",
+        "5053/udp:5053",
+        "9661:19661",
+        "9661/udp:19661",
+        "8080/tcp:8080"
+    ]
+
+    expected_port_map = {
+        (9101,"TCP") : 0,
+        (5053,"TCP") : 5053,
+        (5053,"UDP") : 5053,
+        (9661,"TCP") : 19661,
+        (9661,"UDP") : 19661,
+        (8080,"TCP") : 8080
+    }  
+
+    for test_case in good_ports:
+        container_ports, port_map = _parse_ports([test_case["in"]])  
+        (cport, hport, proto) = test_case["ex"]
+        assert container_ports == [(cport, proto)]
+        assert port_map == {(cport, proto) : hport}
+
+    for port in bad_ports:
+        with pytest.raises(ValueError):
+            _parse_ports([port])
+
+    container_ports, port_map = _parse_ports(port_list)
+    assert port_map == expected_port_map
+
+def test_create_container():
+    from k8sclient.k8sclient import _create_container_object
+    from kubernetes import client
+
+    container = _create_container_object("c1","nginx",False, container_ports=[(80, "TCP"), (53, "UDP")])
+
+    assert container.ports[0].container_port == 80 and container.ports[0].protocol == "TCP"
+    assert container.ports[1].container_port == 53 and container.ports[1].protocol == "UDP"