Kubernetes NodePort must have 'name' if multiple created 57/59857/3
authorRodolfo Alonso Hernandez <rodolfo.alonso.hernandez@intel.com>
Mon, 16 Jul 2018 13:38:26 +0000 (14:38 +0100)
committerRodolfo Alonso Hernandez <rodolfo.alonso.hernandez@intel.com>
Thu, 19 Jul 2018 08:07:23 +0000 (08:07 +0000)
In Kubernetes context, service NodePort can contain more than one port
defined. Actually, by default port SSH (22) is always created. If more
than one port is defined in the service template, 'name' parameter is
mandatory.

Names must be lowercase, containing alphanumeric characters or '-'.

Verification regex used by Kubernetes: [a-z0-9]([-a-z0-9]*[a-z0-9])?

JIRA: YARDSTICK-1324

Change-Id: I82791761d8eae24196c2f16aee9900af28d44c57
Signed-off-by: Rodolfo Alonso Hernandez <rodolfo.alonso.hernandez@intel.com>
yardstick/common/exceptions.py
yardstick/orchestrator/kubernetes.py
yardstick/tests/unit/orchestrator/test_kubernetes.py

index c25acba..9397d47 100644 (file)
@@ -231,6 +231,16 @@ class KubernetesServiceObjectNotDefined(YardstickException):
     message = 'ServiceObject is not defined'
 
 
+class KubernetesServiceObjectDefinitionError(YardstickException):
+    message = ('Kubernetes Service object definition error, missing '
+               'parameters: %(missing_parameters)s')
+
+
+class KubernetesServiceObjectNameError(YardstickException):
+    message = ('Kubernetes Service object name "%(name)s" does not comply'
+               'naming convention')
+
+
 class KubernetesCRDObjectDefinitionError(YardstickException):
     message = ('Kubernetes Custom Resource Definition Object error, missing '
                'parameters: %(missing_parameters)s')
index 9883290..1da1779 100644 (file)
@@ -8,6 +8,7 @@
 ##############################################################################
 
 import copy
+import re
 
 from oslo_serialization import jsonutils
 
@@ -234,6 +235,9 @@ class ReplicationControllerObject(object):
 
 class ServiceNodePortObject(object):
 
+    MANDATORY_PARAMETERS = {'port', 'name'}
+    NAME_REGEX = re.compile(r'^[a-z0-9]([-a-z0-9]*[a-z0-9])?$')
+
     def __init__(self, name, **kwargs):
         """Service kind "NodePort" object
 
@@ -251,19 +255,27 @@ class ServiceNodePortObject(object):
             }
         }
 
-        self._add_port(22, protocol='TCP')
+        self._add_port(22, 'ssh', protocol='TCP')
         node_ports = copy.deepcopy(kwargs.get('node_ports', []))
         for port in node_ports:
+            if not self.MANDATORY_PARAMETERS.issubset(port.keys()):
+                missing_parameters = ', '.join(
+                    str(param) for param in
+                    (self.MANDATORY_PARAMETERS - set(port.keys())))
+                raise exceptions.KubernetesServiceObjectDefinitionError(
+                    missing_parameters=missing_parameters)
             port_number = port.pop('port')
-            self._add_port(port_number, **port)
+            name = port.pop('name')
+            if not self.NAME_REGEX.match(name):
+                raise exceptions.KubernetesServiceObjectNameError(name=name)
+            self._add_port(port_number, name, **port)
 
-    def _add_port(self, port, protocol=None, name=None, targetPort=None,
+    def _add_port(self, port, name, protocol=None, targetPort=None,
                   nodePort=None):
-        _port = {'port': port}
+        _port = {'port': port,
+                 'name': name}
         if protocol:
             _port['protocol'] = protocol
-        if name:
-            _port['name'] = name
         if targetPort:
             _port['targetPort'] = targetPort
         if nodePort:
index 8d351e4..9da421a 100644 (file)
@@ -513,24 +513,52 @@ class ServiceNodePortObjectTestCase(base.BaseUnitTestCase):
     def test__init(self):
         with mock.patch.object(kubernetes.ServiceNodePortObject, '_add_port') \
                 as mock_add_port:
-            kubernetes.ServiceNodePortObject('fake_name',
-                                             node_ports=[{'port': 80}])
+            kubernetes.ServiceNodePortObject(
+                'fake_name', node_ports=[{'port': 80, 'name': 'web'}])
 
-        mock_add_port.assert_has_calls([mock.call(22, protocol='TCP'),
-                                        mock.call(80)])
+        mock_add_port.assert_has_calls([mock.call(22, 'ssh', protocol='TCP'),
+                                        mock.call(80, 'web')])
+
+    @mock.patch.object(kubernetes.ServiceNodePortObject, '_add_port')
+    def test__init_missing_mandatory_parameters(self, *args):
+        with self.assertRaises(
+                exceptions.KubernetesServiceObjectDefinitionError):
+            kubernetes.ServiceNodePortObject(
+                'fake_name', node_ports=[{'port': 80}])
+        with self.assertRaises(
+                exceptions.KubernetesServiceObjectDefinitionError):
+            kubernetes.ServiceNodePortObject(
+                'fake_name', node_ports=[{'name': 'web'}])
+
+    @mock.patch.object(kubernetes.ServiceNodePortObject, '_add_port')
+    def test__init_missing_bad_name(self, *args):
+        with self.assertRaises(
+                exceptions.KubernetesServiceObjectNameError):
+            kubernetes.ServiceNodePortObject(
+                'fake_name', node_ports=[{'port': 80, 'name': '-web'}])
+        with self.assertRaises(
+                exceptions.KubernetesServiceObjectNameError):
+            kubernetes.ServiceNodePortObject(
+                'fake_name', node_ports=[{'port': 80, 'name': 'Web'}])
+        with self.assertRaises(
+                exceptions.KubernetesServiceObjectNameError):
+            kubernetes.ServiceNodePortObject(
+                'fake_name', node_ports=[{'port': 80, 'name': 'web-'}])
 
     def test__add_port(self):
         nodeport_object = kubernetes.ServiceNodePortObject('fake_name')
-        port_ssh = {'port': 22,
-                    'protocol': 'TCP',}
+        port_ssh = {'name': 'ssh',
+                    'port': 22,
+                    'protocol': 'TCP'}
         port_definition = {'port': 80,
                            'protocol': 'TCP',
                            'name': 'web',
                            'targetPort': 10080,
                            'nodePort': 30080}
         port = copy.deepcopy(port_definition)
-        port.pop('port')
-        nodeport_object._add_port(80, **port)
+        _port = port.pop('port')
+        name = port.pop('name')
+        nodeport_object._add_port(_port, name, **port)
         self.assertEqual([port_ssh, port_definition],
                          nodeport_object.template['spec']['ports'])