Create user defined "NodePort" services per pod 29/57529/5
authorRodolfo Alonso Hernandez <rodolfo.alonso.hernandez@intel.com>
Wed, 16 May 2018 16:50:09 +0000 (17:50 +0100)
committerRodolfo Alonso Hernandez <rodolfo.alonso.hernandez@intel.com>
Thu, 14 Jun 2018 07:16:01 +0000 (07:16 +0000)
This feature will give the user the ability to create user defined "NodePort"
services per pod, a part from the default SSH port created, by default, for
each pod created.

Example of Kubernetes yaml definition:
  apiVersion: v1
  kind: Servicemeta
  data:
    name: pod-1-service-nodeport
  spec:
    type: NodePort
    ports:
      - name: web
        port: 80
        targetPort: 8888
        nodePort: 33333
    clusterIP: 10.254.0.8
    selector:
      app: pod-1

Example of Yardstick test case definition:
  context:
    type: Kubernetes
    servers:
      host:
        containers:
          - image: ...
            securityContext:
              allowPrivilegeEscalation: false
          - image: ...
        node_ports:
          - port: <port number>         # Mandatory
            name: <port name>           # Optional
            targetPort: <port name>     # Optional, default: targetPort=port
            nodePort: <port number>     # Optional, assigned by Kubernetes

Kubernetes service, type "NodePort" [1]

[1] https://kubernetes.io/docs/concepts/services-networking/service/#type-nodeport

JIRA: YARDSTICK-1181

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

index 120c40b..a595de8 100644 (file)
@@ -182,33 +182,49 @@ class ReplicationControllerObject(object):
                              annotations)
 
 
-class ServiceObject(object):
+class ServiceNodePortObject(object):
 
-    def __init__(self, name):
-        self.name = '{}-service'.format(name)
+    def __init__(self, name, **kwargs):
+        """Service kind "NodePort" object
+
+        :param name: (string) name of the Service
+        :param kwargs: (dict) node_ports -> (list) port, name, targetPort,
+                                                   nodePort
+        """
+        self._name = '{}-service'.format(name)
         self.template = {
-            'metadata': {
-                'name': '{}-service'.format(name)
-            },
+            'metadata': {'name': '{}-service'.format(name)},
             'spec': {
                 'type': 'NodePort',
-                'ports': [
-                    {
-                        'port': 22,
-                        'protocol': 'TCP'
-                    }
-                ],
-                'selector': {
-                    'app': name
-                }
+                'ports': [],
+                'selector': {'app': name}
             }
         }
 
+        self._add_port(22, protocol='TCP')
+        node_ports = copy.deepcopy(kwargs.get('node_ports', []))
+        for port in node_ports:
+            port_number = port.pop('port')
+            self._add_port(port_number, **port)
+
+    def _add_port(self, port, protocol=None, name=None, targetPort=None,
+                  nodePort=None):
+        _port = {'port': port}
+        if protocol:
+            _port['protocol'] = protocol
+        if name:
+            _port['name'] = name
+        if targetPort:
+            _port['targetPort'] = targetPort
+        if nodePort:
+            _port['nodePort'] = nodePort
+        self.template['spec']['ports'].append(_port)
+
     def create(self):
         k8s_utils.create_service(self.template)
 
     def delete(self):
-        k8s_utils.delete_service(self.name)
+        k8s_utils.delete_service(self._name)
 
 
 class CustomResourceDefinitionObject(object):
@@ -359,11 +375,12 @@ class KubernetesTemplate(object):
         self.name = name
         self.ssh_key = '{}-key'.format(name)
 
-        self.rcs = [self._get_rc_name(rc) for rc in servers_cfg]
+        self.rcs = {self._get_rc_name(rc): cfg
+                    for rc, cfg in servers_cfg.items()}
         self.k8s_objs = [ReplicationControllerObject(
-            self._get_rc_name(rc), ssh_key=self.ssh_key, **cfg)
-            for rc, cfg in servers_cfg.items()]
-        self.service_objs = [ServiceObject(s) for s in self.rcs]
+            rc, ssh_key=self.ssh_key, **cfg) for rc, cfg in self.rcs.items()]
+        self.service_objs = [ServiceNodePortObject(rc, **cfg)
+                             for rc, cfg in self.rcs.items()]
         self.crd = [CustomResourceDefinitionObject(self.name, **crd)
                     for crd in crd_cfg]
         self.network_objs = [NetworkObject(**nobj) for nobj in networks_cfg]
index 0d698c5..fd7b4f2 100644 (file)
@@ -163,12 +163,12 @@ class KubernetesTestCase(unittest.TestCase):
         self.k8s_context._get_node_ip()
         mock_get_node_list.assert_called_once()
 
-    @mock.patch('yardstick.orchestrator.kubernetes.ServiceObject.create')
+    @mock.patch.object(orchestrator_kubernetes.ServiceNodePortObject, 'create')
     def test_create_services(self, mock_create):
         self.k8s_context._create_services()
         mock_create.assert_called()
 
-    @mock.patch('yardstick.orchestrator.kubernetes.ServiceObject.delete')
+    @mock.patch.object(orchestrator_kubernetes.ServiceNodePortObject, 'delete')
     def test_delete_services(self, mock_delete):
         self.k8s_context._delete_services()
         mock_delete.assert_called()
index e8d5abf..70d17e2 100644 (file)
@@ -399,3 +399,43 @@ class NetworkObjectTestCase(base.BaseUnitTestCase):
         net_obj.delete()
         mock_delete_network.assert_called_once_with(
             'scope', 'group', 'version', 'plural', 'name')
+
+
+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}])
+
+        mock_add_port.assert_has_calls([mock.call(22, protocol='TCP'),
+                                        mock.call(80)])
+
+    def test__add_port(self):
+        nodeport_object = kubernetes.ServiceNodePortObject('fake_name')
+        port_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)
+        self.assertEqual([port_ssh, port_definition],
+                         nodeport_object.template['spec']['ports'])
+
+    @mock.patch.object(kubernetes_utils, 'create_service')
+    def test_create(self, mock_create_service):
+        nodeport_object = kubernetes.ServiceNodePortObject('fake_name')
+        nodeport_object.template = 'fake_template'
+        nodeport_object.create()
+        mock_create_service.assert_called_once_with('fake_template')
+
+    @mock.patch.object(kubernetes_utils, 'delete_service')
+    def test_delete(self, mock_delete_service):
+        nodeport_object = kubernetes.ServiceNodePortObject('fake_name')
+        nodeport_object.delete()
+        mock_delete_service.assert_called_once_with('fake_name-service')