Merge "the recovery action of "baremetal down" should be triggered mandatory"
[yardstick.git] / yardstick / orchestrator / kubernetes.py
index 07a7ab1..b0b93a3 100644 (file)
@@ -8,8 +8,10 @@
 ##############################################################################
 
 import copy
+import re
 
 from oslo_serialization import jsonutils
+import six
 
 from yardstick.common import constants
 from yardstick.common import exceptions
@@ -21,19 +23,34 @@ class ContainerObject(object):
 
     SSH_MOUNT_PATH = '/tmp/.ssh/'
     IMAGE_DEFAULT = 'openretriever/yardstick'
-    COMMAND_DEFAULT = '/bin/bash'
-    RESOURCES = ['requests', 'limits']
+    COMMAND_DEFAULT = ['/bin/bash', '-c']
+    RESOURCES = ('requests', 'limits')
+    PORT_OPTIONS = ('containerPort', 'hostIP', 'hostPort', 'name', 'protocol')
+    IMAGE_PULL_POLICY = ('Always', 'IfNotPresent', 'Never')
 
     def __init__(self, name, ssh_key, **kwargs):
         self._name = name
         self._ssh_key = ssh_key
         self._image = kwargs.get('image', self.IMAGE_DEFAULT)
-        self._command = [kwargs.get('command', self.COMMAND_DEFAULT)]
-        self._args = kwargs.get('args', [])
+        self._command = self._parse_commands(
+            kwargs.get('command', self.COMMAND_DEFAULT))
+        self._args = self._parse_commands(kwargs.get('args', []))
         self._volume_mounts = kwargs.get('volumeMounts', [])
         self._security_context = kwargs.get('securityContext')
         self._env = kwargs.get('env', [])
         self._resources = kwargs.get('resources', {})
+        self._ports = kwargs.get('ports', [])
+        self._image_pull_policy = kwargs.get('imagePullPolicy')
+        self._tty = kwargs.get('tty')
+        self._stdin = kwargs.get('stdin')
+
+    @staticmethod
+    def _parse_commands(command):
+        if isinstance(command, six.string_types):
+            return [command]
+        elif isinstance(command, list):
+            return command
+        raise exceptions.KubernetesContainerCommandType()
 
     def _create_volume_mounts(self):
         """Return all "volumeMounts" items per container"""
@@ -66,17 +83,36 @@ class ContainerObject(object):
             for env in self._env:
                 container['env'].append({'name': env['name'],
                                          'value': env['value']})
+        if self._ports:
+            container['ports'] = []
+            for port in self._ports:
+                if 'containerPort' not in port.keys():
+                    raise exceptions.KubernetesContainerPortNotDefined(
+                        port=port)
+                _port = {port_option: value for port_option, value
+                         in port.items() if port_option in self.PORT_OPTIONS}
+                container['ports'].append(_port)
         if self._resources:
             container['resources'] = {}
             for res in (res for res in self._resources if
                         res in self.RESOURCES):
                 container['resources'][res] = self._resources[res]
+        if self._image_pull_policy:
+            if self._image_pull_policy not in self.IMAGE_PULL_POLICY:
+                raise exceptions.KubernetesContainerWrongImagePullPolicy()
+            container['imagePullPolicy'] = self._image_pull_policy
+        if self._stdin is not None:
+            container['stdin'] = self._stdin
+        if self._tty is not None:
+            container['tty'] = self._tty
         return container
 
 
 class ReplicationControllerObject(object):
 
     SSHKEY_DEFAULT = 'yardstick_key'
+    RESTART_POLICY = ('Always', 'OnFailure', 'Never')
+    TOLERATIONS_KEYS = ('key', 'value', 'effect', 'operator')
 
     def __init__(self, name, **kwargs):
         super(ReplicationControllerObject, self).__init__()
@@ -87,6 +123,11 @@ class ReplicationControllerObject(object):
         self._volumes = parameters.pop('volumes', [])
         self._security_context = parameters.pop('securityContext', None)
         self._networks = parameters.pop('networks', [])
+        self._tolerations = parameters.pop('tolerations', [])
+        self._restart_policy = parameters.pop('restartPolicy', 'Always')
+        if self._restart_policy not in self.RESTART_POLICY:
+            raise exceptions.KubernetesWrongRestartPolicy(
+                rpolicy=self._restart_policy)
 
         containers = parameters.pop('containers', None)
         if containers:
@@ -112,7 +153,9 @@ class ReplicationControllerObject(object):
                     "spec": {
                         "containers": [],
                         "volumes": [],
-                        "nodeSelector": {}
+                        "nodeSelector": {},
+                        "restartPolicy": self._restart_policy,
+                        "tolerations": []
                     }
                 }
             }
@@ -124,6 +167,11 @@ class ReplicationControllerObject(object):
         self._add_volumes()
         self._add_security_context()
         self._add_networks()
+        self._add_tolerations()
+
+    @property
+    def networks(self):
+        return self._networks
 
     def get_template(self):
         return self.template
@@ -194,9 +242,24 @@ class ReplicationControllerObject(object):
                              'spec.template.metadata.annotations',
                              annotations)
 
+    def _add_tolerations(self):
+        tolerations = []
+        for tol in self._tolerations:
+            tolerations.append({k: tol[k] for k in tol
+                                if k in self.TOLERATIONS_KEYS})
+
+        tolerations = ([{'operator': 'Exists'}] if not tolerations
+                       else tolerations)
+        utils.set_dict_value(self.template,
+                             'spec.template.spec.tolerations',
+                             tolerations)
+
 
 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
 
@@ -214,19 +277,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:
@@ -237,7 +308,7 @@ class ServiceNodePortObject(object):
         k8s_utils.create_service(self.template)
 
     def delete(self):
-        k8s_utils.delete_service(self._name)
+        k8s_utils.delete_service(self._name, skip_codes=[404])
 
 
 class CustomResourceDefinitionObject(object):
@@ -278,15 +349,15 @@ class CustomResourceDefinitionObject(object):
         k8s_utils.create_custom_resource_definition(self._template)
 
     def delete(self):
-        k8s_utils.delete_custom_resource_definition(self._name)
+        k8s_utils.delete_custom_resource_definition(self._name, skip_codes=[404])
 
 
 class NetworkObject(object):
 
-    MANDATORY_PARAMETERS = {'name', 'plugin', 'args'}
+    MANDATORY_PARAMETERS = {'plugin', 'args'}
     KIND = 'Network'
 
-    def __init__(self, **kwargs):
+    def __init__(self, name, **kwargs):
         if not self.MANDATORY_PARAMETERS.issubset(kwargs):
             missing_parameters = ', '.join(
                 str(param) for param in
@@ -294,7 +365,7 @@ class NetworkObject(object):
             raise exceptions.KubernetesNetworkObjectDefinitionError(
                 missing_parameters=missing_parameters)
 
-        self._name = kwargs['name']
+        self._name = name
         self._plugin = kwargs['plugin']
         self._args = kwargs['args']
         self._crd = None
@@ -366,11 +437,11 @@ class NetworkObject(object):
 
     def create(self):
         k8s_utils.create_network(self.scope, self.group, self.version,
-                                 self.plural, self.template)
+                                 self.plural, self.template, self._name)
 
     def delete(self):
         k8s_utils.delete_network(self.scope, self.group, self.version,
-                                 self.plural, self._name)
+                                 self.plural, self._name, skip_codes=[404])
 
 
 class KubernetesTemplate(object):
@@ -384,19 +455,20 @@ class KubernetesTemplate(object):
         context_cfg = copy.deepcopy(context_cfg)
         servers_cfg = context_cfg.pop('servers', {})
         crd_cfg = context_cfg.pop('custom_resources', [])
-        networks_cfg = context_cfg.pop('networks', [])
+        networks_cfg = context_cfg.pop('networks', {})
         self.name = name
         self.ssh_key = '{}-key'.format(name)
 
         self.rcs = {self._get_rc_name(rc): cfg
                     for rc, cfg in servers_cfg.items()}
-        self.k8s_objs = [ReplicationControllerObject(
+        self.rc_objs = [ReplicationControllerObject(
             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]
+        self.network_objs = [NetworkObject(net_name, **net_data)
+                             for net_name, net_data in networks_cfg.items()]
         self.pods = []
 
     def _get_rc_name(self, rc_name):
@@ -408,3 +480,8 @@ class KubernetesTemplate(object):
                      if p.metadata.name.startswith(s)]
 
         return self.pods
+
+    def get_rc_by_name(self, rc_name):
+        """Returns a ``ReplicationControllerObject``, searching by name"""
+        for rc in (rc for rc in self.rc_objs if rc.name == rc_name):
+            return rc