From d3ee90f9dd7335a3e13d1c4d71cad2b3b5c825fd Mon Sep 17 00:00:00 2001 From: Rodolfo Alonso Hernandez Date: Mon, 16 Jul 2018 14:38:26 +0100 Subject: Kubernetes NodePort must have 'name' if multiple created 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 --- yardstick/common/exceptions.py | 10 +++++ yardstick/orchestrator/kubernetes.py | 24 +++++++++--- .../tests/unit/orchestrator/test_kubernetes.py | 44 ++++++++++++++++++---- 3 files changed, 64 insertions(+), 14 deletions(-) diff --git a/yardstick/common/exceptions.py b/yardstick/common/exceptions.py index c25acbaf8..9397d4772 100644 --- a/yardstick/common/exceptions.py +++ b/yardstick/common/exceptions.py @@ -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') diff --git a/yardstick/orchestrator/kubernetes.py b/yardstick/orchestrator/kubernetes.py index 98832908c..1da177945 100644 --- a/yardstick/orchestrator/kubernetes.py +++ b/yardstick/orchestrator/kubernetes.py @@ -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: diff --git a/yardstick/tests/unit/orchestrator/test_kubernetes.py b/yardstick/tests/unit/orchestrator/test_kubernetes.py index 8d351e419..9da421a4a 100644 --- a/yardstick/tests/unit/orchestrator/test_kubernetes.py +++ b/yardstick/tests/unit/orchestrator/test_kubernetes.py @@ -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']) -- cgit 1.2.3-korg