Merge "Add "securityContext" parameter in Kubernetes context"
[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         self._security_context = kwargs.get('securityContext')
31
32     def _create_volume_mounts(self):
33         """Return all "volumeMounts" items per container"""
34         volume_mounts_items = [self._create_volume_mounts_item(vol)
35                                for vol in self._volume_mounts]
36         ssh_vol = {'name': self._ssh_key,
37                    'mountPath': self.SSH_MOUNT_PATH}
38         volume_mounts_items.append(self._create_volume_mounts_item(ssh_vol))
39         return volume_mounts_items
40
41     @staticmethod
42     def _create_volume_mounts_item(volume_mount):
43         """Create a "volumeMounts" item"""
44         return {'name': volume_mount['name'],
45                 'mountPath': volume_mount['mountPath'],
46                 'readOnly': volume_mount.get('readOnly', False)}
47
48     def get_container_item(self):
49         """Create a "container" item"""
50         container_name = '{}-container'.format(self._name)
51         container = {'args': self._args,
52                      'command': self._command,
53                      'image': self._image,
54                      'name': container_name,
55                      'volumeMounts': self._create_volume_mounts()}
56         if self._security_context:
57             container['securityContext'] = self._security_context
58         return container
59
60
61 class KubernetesObject(object):
62
63     SSHKEY_DEFAULT = 'yardstick_key'
64
65     def __init__(self, name, **kwargs):
66         super(KubernetesObject, self).__init__()
67         parameters = copy.deepcopy(kwargs)
68         self.name = name
69         self.node_selector = parameters.pop('nodeSelector', {})
70         self.ssh_key = parameters.pop('ssh_key', self.SSHKEY_DEFAULT)
71         self._volumes = parameters.pop('volumes', [])
72         self._security_context = parameters.pop('securityContext', None)
73
74         containers = parameters.pop('containers', None)
75         if containers:
76             self._containers = [
77                 ContainerObject(self.name, self.ssh_key, **container)
78                 for container in containers]
79         else:
80             self._containers = [
81                 ContainerObject(self.name, self.ssh_key, **parameters)]
82
83         self.template = {
84             "apiVersion": "v1",
85             "kind": "ReplicationController",
86             "metadata": {
87                 "name": ""
88             },
89             "spec": {
90                 "replicas": 1,
91                 "template": {
92                     "metadata": {
93                         "labels": {
94                             "app": name
95                         }
96                     },
97                     "spec": {
98                         "containers": [],
99                         "volumes": [],
100                         "nodeSelector": {}
101                     }
102                 }
103             }
104         }
105
106         self._change_value_according_name(name)
107         self._add_containers()
108         self._add_node_selector()
109         self._add_volumes()
110         self._add_security_context()
111
112     def get_template(self):
113         return self.template
114
115     def _change_value_according_name(self, name):
116         utils.set_dict_value(self.template, 'metadata.name', name)
117
118         utils.set_dict_value(self.template,
119                              'spec.template.metadata.labels.app',
120                              name)
121
122     def _add_containers(self):
123         containers = [container.get_container_item()
124                       for container in self._containers]
125         utils.set_dict_value(self.template,
126                              'spec.template.spec.containers',
127                              containers)
128
129     def _add_node_selector(self):
130         utils.set_dict_value(self.template,
131                              'spec.template.spec.nodeSelector',
132                              self.node_selector)
133
134     def _add_volumes(self):
135         """Add "volume" items to container specs, including the SSH one"""
136         volume_items = [self._create_volume_item(vol) for vol in self._volumes]
137         volume_items.append(self._create_ssh_key_volume())
138         utils.set_dict_value(self.template,
139                              'spec.template.spec.volumes',
140                              volume_items)
141
142     def _create_ssh_key_volume(self):
143         """Create a "volume" item of type "configMap" for the SSH key"""
144         return {'name': self.ssh_key,
145                 'configMap': {'name': self.ssh_key}}
146
147     @staticmethod
148     def _create_volume_item(volume):
149         """Create a "volume" item"""
150         volume = copy.deepcopy(volume)
151         name = volume.pop('name')
152         for key in (k for k in volume if k in k8s_utils.get_volume_types()):
153             type_name = key
154             type_data = volume[key]
155             break
156         else:
157             raise exceptions.KubernetesTemplateInvalidVolumeType(volume=volume)
158
159         return {'name': name,
160                 type_name: type_data}
161
162     def _add_security_context(self):
163         if self._security_context:
164             utils.set_dict_value(self.template,
165                                  'spec.template.spec.securityContext',
166                                  self._security_context)
167
168
169 class ServiceObject(object):
170
171     def __init__(self, name):
172         self.name = '{}-service'.format(name)
173         self.template = {
174             'metadata': {
175                 'name': '{}-service'.format(name)
176             },
177             'spec': {
178                 'type': 'NodePort',
179                 'ports': [
180                     {
181                         'port': 22,
182                         'protocol': 'TCP'
183                     }
184                 ],
185                 'selector': {
186                     'app': name
187                 }
188             }
189         }
190
191     def create(self):
192         k8s_utils.create_service(self.template)
193
194     def delete(self):
195         k8s_utils.delete_service(self.name)
196
197
198 class KubernetesTemplate(object):
199
200     def __init__(self, name, context_cfg):
201         """KubernetesTemplate object initialization
202
203         :param name: (str) name of the Kubernetes context
204         :param context_cfg: (dict) context definition
205         """
206         context_cfg = copy.deepcopy(context_cfg)
207         servers_cfg = context_cfg.pop('servers', {})
208         self.name = name
209         self.ssh_key = '{}-key'.format(name)
210
211         self.rcs = [self._get_rc_name(rc) for rc in servers_cfg]
212         self.k8s_objs = [KubernetesObject(self._get_rc_name(rc),
213                                           ssh_key=self.ssh_key,
214                                           **cfg)
215                          for rc, cfg in servers_cfg.items()]
216         self.service_objs = [ServiceObject(s) for s in self.rcs]
217
218         self.pods = []
219
220     def _get_rc_name(self, rc_name):
221         return '{}-{}'.format(rc_name, self.name)
222
223     def get_rc_pods(self):
224         resp = k8s_utils.get_pod_list()
225         self.pods = [p.metadata.name for p in resp.items for s in self.rcs
226                      if p.metadata.name.startswith(s)]
227
228         return self.pods