Merge "[Bug fix] Execute teardown if SLA is set to "assert""
[yardstick.git] / yardstick / orchestrator / kubernetes.py
1 ##############################################################################
2 # Copyright (c) 2017 Huawei Technologies Co.,Ltd.
3 #
4 # All rights reserved. This program and the accompanying materials
5 # are made available under the terms of the Apache License, Version 2.0
6 # which accompanies this distribution, and is available at
7 # http://www.apache.org/licenses/LICENSE-2.0
8 ##############################################################################
9
10 import copy
11
12 from yardstick.common import exceptions
13 from yardstick.common import utils
14 from yardstick.common import kubernetes_utils as k8s_utils
15
16
17 class ContainerObject(object):
18
19     SSH_MOUNT_PATH = '/tmp/.ssh/'
20     IMAGE_DEFAULT = 'openretriever/yardstick'
21     COMMAND_DEFAULT = '/bin/bash'
22
23     def __init__(self, name, ssh_key, **kwargs):
24         self._name = name
25         self._ssh_key = ssh_key
26         self._image = kwargs.get('image', self.IMAGE_DEFAULT)
27         self._command = [kwargs.get('command', self.COMMAND_DEFAULT)]
28         self._args = kwargs.get('args', [])
29         self._volume_mounts = kwargs.get('volumeMounts', [])
30
31     def _create_volume_mounts(self):
32         """Return all "volumeMounts" items per container"""
33         volume_mounts_items = [self._create_volume_mounts_item(vol)
34                                for vol in self._volume_mounts]
35         ssh_vol = {'name': self._ssh_key,
36                    'mountPath': self.SSH_MOUNT_PATH}
37         volume_mounts_items.append(self._create_volume_mounts_item(ssh_vol))
38         return volume_mounts_items
39
40     @staticmethod
41     def _create_volume_mounts_item(volume_mount):
42         """Create a "volumeMounts" item"""
43         return {'name': volume_mount['name'],
44                 'mountPath': volume_mount['mountPath'],
45                 'readOnly': volume_mount.get('readOnly', False)}
46
47     def get_container_item(self):
48         """Create a "container" item"""
49         container_name = '{}-container'.format(self._name)
50         return {'args': self._args,
51                 'command': self._command,
52                 'image': self._image,
53                 'name': container_name,
54                 'volumeMounts': self._create_volume_mounts()}
55
56
57 class KubernetesObject(object):
58
59     SSHKEY_DEFAULT = 'yardstick_key'
60
61     def __init__(self, name, **kwargs):
62         super(KubernetesObject, self).__init__()
63         parameters = copy.deepcopy(kwargs)
64         self.name = name
65         self.node_selector = parameters.pop('nodeSelector', {})
66         self.ssh_key = parameters.pop('ssh_key', self.SSHKEY_DEFAULT)
67         self._volumes = parameters.pop('volumes', [])
68
69         containers = parameters.pop('containers', None)
70         if containers:
71             self._containers = [
72                 ContainerObject(self.name, self.ssh_key, **container)
73                 for container in containers]
74         else:
75             self._containers = [
76                 ContainerObject(self.name, self.ssh_key, **parameters)]
77
78         self.template = {
79             "apiVersion": "v1",
80             "kind": "ReplicationController",
81             "metadata": {
82                 "name": ""
83             },
84             "spec": {
85                 "replicas": 1,
86                 "template": {
87                     "metadata": {
88                         "labels": {
89                             "app": name
90                         }
91                     },
92                     "spec": {
93                         "containers": [],
94                         "volumes": [],
95                         "nodeSelector": {}
96                     }
97                 }
98             }
99         }
100
101         self._change_value_according_name(name)
102         self._add_containers()
103         self._add_node_selector()
104         self._add_volumes()
105
106     def get_template(self):
107         return self.template
108
109     def _change_value_according_name(self, name):
110         utils.set_dict_value(self.template, 'metadata.name', name)
111
112         utils.set_dict_value(self.template,
113                              'spec.template.metadata.labels.app',
114                              name)
115
116     def _add_containers(self):
117         containers = [container.get_container_item()
118                       for container in self._containers]
119         utils.set_dict_value(self.template,
120                              'spec.template.spec.containers',
121                              containers)
122
123     def _add_node_selector(self):
124         utils.set_dict_value(self.template,
125                              'spec.template.spec.nodeSelector',
126                              self.node_selector)
127
128     def _add_volumes(self):
129         """Add "volume" items to container specs, including the SSH one"""
130         volume_items = [self._create_volume_item(vol) for vol in self._volumes]
131         volume_items.append(self._create_ssh_key_volume())
132         utils.set_dict_value(self.template,
133                              'spec.template.spec.volumes',
134                              volume_items)
135
136     def _create_ssh_key_volume(self):
137         """Create a "volume" item of type "configMap" for the SSH key"""
138         return {'name': self.ssh_key,
139                 'configMap': {'name': self.ssh_key}}
140
141     @staticmethod
142     def _create_volume_item(volume):
143         """Create a "volume" item"""
144         volume = copy.deepcopy(volume)
145         name = volume.pop('name')
146         for key in (k for k in volume if k in k8s_utils.get_volume_types()):
147             type_name = key
148             type_data = volume[key]
149             break
150         else:
151             raise exceptions.KubernetesTemplateInvalidVolumeType(volume=volume)
152
153         return {'name': name,
154                 type_name: type_data}
155
156
157 class ServiceObject(object):
158
159     def __init__(self, name):
160         self.name = '{}-service'.format(name)
161         self.template = {
162             'metadata': {
163                 'name': '{}-service'.format(name)
164             },
165             'spec': {
166                 'type': 'NodePort',
167                 'ports': [
168                     {
169                         'port': 22,
170                         'protocol': 'TCP'
171                     }
172                 ],
173                 'selector': {
174                     'app': name
175                 }
176             }
177         }
178
179     def create(self):
180         k8s_utils.create_service(self.template)
181
182     def delete(self):
183         k8s_utils.delete_service(self.name)
184
185
186 class KubernetesTemplate(object):
187
188     def __init__(self, name, context_cfg):
189         """KubernetesTemplate object initialization
190
191         :param name: (str) name of the Kubernetes context
192         :param context_cfg: (dict) context definition
193         """
194         context_cfg = copy.deepcopy(context_cfg)
195         servers_cfg = context_cfg.pop('servers', {})
196         self.name = name
197         self.ssh_key = '{}-key'.format(name)
198
199         self.rcs = [self._get_rc_name(rc) for rc in servers_cfg]
200         self.k8s_objs = [KubernetesObject(self._get_rc_name(rc),
201                                           ssh_key=self.ssh_key,
202                                           **cfg)
203                          for rc, cfg in servers_cfg.items()]
204         self.service_objs = [ServiceObject(s) for s in self.rcs]
205
206         self.pods = []
207
208     def _get_rc_name(self, rc_name):
209         return '{}-{}'.format(rc_name, self.name)
210
211     def get_rc_pods(self):
212         resp = k8s_utils.get_pod_list()
213         self.pods = [p.metadata.name for p in resp.items for s in self.rcs
214                      if p.metadata.name.startswith(s)]
215
216         return self.pods