From 74aaa02970e8c4ee3f134ee378fd5b7167cd06a0 Mon Sep 17 00:00:00 2001 From: Jack Lucas Date: Fri, 12 Oct 2018 11:30:05 -0400 Subject: Add support for UDP port mapping Issue-ID: DCAEGEN2-796 Change-Id: I09038d0bc7154b989f9ce9e328da57aac0020597 Signed-off-by: Jack Lucas --- .gitignore | 1 + k8s/README.md | 4 ++- k8s/k8sclient/k8sclient.py | 43 ++++++++++++++++++--------- k8s/pom.xml | 2 +- k8s/setup.py | 2 +- k8s/tests/test_k8sclient.py | 71 ++++++++++++++++++++++++++++++++++++++++++++- 6 files changed, 105 insertions(+), 18 deletions(-) diff --git a/.gitignore b/.gitignore index d11997c..22ce2d9 100644 --- a/.gitignore +++ b/.gitignore @@ -6,6 +6,7 @@ .project .pydevproject venv +.vscode/ # Byte-compiled / optimized / DLL files diff --git a/k8s/README.md b/k8s/README.md index 2f4b3a6..dfe9937 100644 --- a/k8s/README.md +++ b/k8s/README.md @@ -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: `:`. +List of strings - Used to bind container ports to host ports. Each item is of the format: `:` or +/:, where 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. diff --git a/k8s/k8sclient/k8sclient.py b/k8s/k8sclient/k8sclient.py index 4040642..806b41e 100644 --- a/k8s/k8sclient/k8sclient.py +++ b/k8s/k8sclient/k8sclient.py @@ -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") diff --git a/k8s/pom.xml b/k8s/pom.xml index ea34f78..39649ca 100644 --- a/k8s/pom.xml +++ b/k8s/pom.xml @@ -28,7 +28,7 @@ ECOMP is a trademark and service mark of AT&T Intellectual Property. org.onap.dcaegen2.platform.plugins k8s k8s-plugin - 1.4.3-SNAPSHOT + 1.4.4-SNAPSHOT http://maven.apache.org UTF-8 diff --git a/k8s/setup.py b/k8s/setup.py index 5897f61..458328d 100644 --- a/k8s/setup.py +++ b/k8s/setup.py @@ -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, diff --git a/k8s/tests/test_k8sclient.py b/k8s/tests/test_k8sclient.py index 00ccfdb..e985def 100644 --- a/k8s/tests/test_k8sclient.py +++ b/k8s/tests/test_k8sclient.py @@ -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" -- cgit 1.2.3-korg