Merge "Add new Kubernetes resource kind: "CustomResourceDefinition""
[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 constants
13 from yardstick.common import exceptions
14 from yardstick.common import utils
15 from yardstick.common import kubernetes_utils as k8s_utils
16
17
18 class ContainerObject(object):
19
20     SSH_MOUNT_PATH = '/tmp/.ssh/'
21     IMAGE_DEFAULT = 'openretriever/yardstick'
22     COMMAND_DEFAULT = '/bin/bash'
23
24     def __init__(self, name, ssh_key, **kwargs):
25         self._name = name
26         self._ssh_key = ssh_key
27         self._image = kwargs.get('image', self.IMAGE_DEFAULT)
28         self._command = [kwargs.get('command', self.COMMAND_DEFAULT)]
29         self._args = kwargs.get('args', [])
30         self._volume_mounts = kwargs.get('volumeMounts', [])
31         self._security_context = kwargs.get('securityContext')
32
33     def _create_volume_mounts(self):
34         """Return all "volumeMounts" items per container"""
35         volume_mounts_items = [self._create_volume_mounts_item(vol)
36                                for vol in self._volume_mounts]
37         ssh_vol = {'name': self._ssh_key,
38                    'mountPath': self.SSH_MOUNT_PATH}
39         volume_mounts_items.append(self._create_volume_mounts_item(ssh_vol))
40         return volume_mounts_items
41
42     @staticmethod
43     def _create_volume_mounts_item(volume_mount):
44         """Create a "volumeMounts" item"""
45         return {'name': volume_mount['name'],
46                 'mountPath': volume_mount['mountPath'],
47                 'readOnly': volume_mount.get('readOnly', False)}
48
49     def get_container_item(self):
50         """Create a "container" item"""
51         container_name = '{}-container'.format(self._name)
52         container = {'args': self._args,
53                      'command': self._command,
54                      'image': self._image,
55                      'name': container_name,
56                      'volumeMounts': self._create_volume_mounts()}
57         if self._security_context:
58             container['securityContext'] = self._security_context
59         return container
60
61
62 class KubernetesObject(object):
63
64     SSHKEY_DEFAULT = 'yardstick_key'
65
66     def __init__(self, name, **kwargs):
67         super(KubernetesObject, self).__init__()
68         parameters = copy.deepcopy(kwargs)
69         self.name = name
70         self.node_selector = parameters.pop('nodeSelector', {})
71         self.ssh_key = parameters.pop('ssh_key', self.SSHKEY_DEFAULT)
72         self._volumes = parameters.pop('volumes', [])
73         self._security_context = parameters.pop('securityContext', None)
74
75         containers = parameters.pop('containers', None)
76         if containers:
77             self._containers = [
78                 ContainerObject(self.name, self.ssh_key, **container)
79                 for container in containers]
80         else:
81             self._containers = [
82                 ContainerObject(self.name, self.ssh_key, **parameters)]
83
84         self.template = {
85             "apiVersion": "v1",
86             "kind": "ReplicationController",
87             "metadata": {
88                 "name": ""
89             },
90             "spec": {
91                 "replicas": 1,
92                 "template": {
93                     "metadata": {
94                         "labels": {
95                             "app": name
96                         }
97                     },
98                     "spec": {
99                         "containers": [],
100                         "volumes": [],
101                         "nodeSelector": {}
102                     }
103                 }
104             }
105         }
106
107         self._change_value_according_name(name)
108         self._add_containers()
109         self._add_node_selector()
110         self._add_volumes()
111         self._add_security_context()
112
113     def get_template(self):
114         return self.template
115
116     def _change_value_according_name(self, name):
117         utils.set_dict_value(self.template, 'metadata.name', name)
118
119         utils.set_dict_value(self.template,
120                              'spec.template.metadata.labels.app',
121                              name)
122
123     def _add_containers(self):
124         containers = [container.get_container_item()
125                       for container in self._containers]
126         utils.set_dict_value(self.template,
127                              'spec.template.spec.containers',
128                              containers)
129
130     def _add_node_selector(self):
131         utils.set_dict_value(self.template,
132                              'spec.template.spec.nodeSelector',
133                              self.node_selector)
134
135     def _add_volumes(self):
136         """Add "volume" items to container specs, including the SSH one"""
137         volume_items = [self._create_volume_item(vol) for vol in self._volumes]
138         volume_items.append(self._create_ssh_key_volume())
139         utils.set_dict_value(self.template,
140                              'spec.template.spec.volumes',
141                              volume_items)
142
143     def _create_ssh_key_volume(self):
144         """Create a "volume" item of type "configMap" for the SSH key"""
145         return {'name': self.ssh_key,
146                 'configMap': {'name': self.ssh_key}}
147
148     @staticmethod
149     def _create_volume_item(volume):
150         """Create a "volume" item"""
151         volume = copy.deepcopy(volume)
152         name = volume.pop('name')
153         for key in (k for k in volume if k in k8s_utils.get_volume_types()):
154             type_name = key
155             type_data = volume[key]
156             break
157         else:
158             raise exceptions.KubernetesTemplateInvalidVolumeType(volume=volume)
159
160         return {'name': name,
161                 type_name: type_data}
162
163     def _add_security_context(self):
164         if self._security_context:
165             utils.set_dict_value(self.template,
166                                  'spec.template.spec.securityContext',
167                                  self._security_context)
168
169
170 class ServiceObject(object):
171
172     def __init__(self, name):
173         self.name = '{}-service'.format(name)
174         self.template = {
175             'metadata': {
176                 'name': '{}-service'.format(name)
177             },
178             'spec': {
179                 'type': 'NodePort',
180                 'ports': [
181                     {
182                         'port': 22,
183                         'protocol': 'TCP'
184                     }
185                 ],
186                 'selector': {
187                     'app': name
188                 }
189             }
190         }
191
192     def create(self):
193         k8s_utils.create_service(self.template)
194
195     def delete(self):
196         k8s_utils.delete_service(self.name)
197
198
199 class CustomResourceDefinitionObject(object):
200
201     MANDATORY_PARAMETERS = {'name'}
202
203     def __init__(self, ctx_name, **kwargs):
204         if not self.MANDATORY_PARAMETERS.issubset(kwargs):
205             missing_parameters = ', '.join(
206                 str(param) for param in
207                 (self.MANDATORY_PARAMETERS - set(kwargs)))
208             raise exceptions.KubernetesCRDObjectDefinitionError(
209                 missing_parameters=missing_parameters)
210
211         singular = kwargs['name']
212         plural = singular + 's'
213         kind = singular.title()
214         version = kwargs.get('version', 'v1')
215         scope = kwargs.get('scope', constants.SCOPE_NAMESPACED)
216         group = ctx_name + '.com'
217         self._name = metadata_name = plural + '.' + group
218
219         self._template = {
220             'metadata': {
221                 'name': metadata_name
222             },
223             'spec': {
224                 'group': group,
225                 'version': version,
226                 'scope': scope,
227                 'names': {'plural': plural,
228                           'singular': singular,
229                           'kind': kind}
230             }
231         }
232
233     def create(self):
234         k8s_utils.create_custom_resource_definition(self._template)
235
236     def delete(self):
237         k8s_utils.delete_custom_resource_definition(self._name)
238
239
240 class KubernetesTemplate(object):
241
242     def __init__(self, name, context_cfg):
243         """KubernetesTemplate object initialization
244
245         :param name: (str) name of the Kubernetes context
246         :param context_cfg: (dict) context definition
247         """
248         context_cfg = copy.deepcopy(context_cfg)
249         servers_cfg = context_cfg.pop('servers', {})
250         crd_cfg = context_cfg.pop('custom_resources', [])
251         self.name = name
252         self.ssh_key = '{}-key'.format(name)
253
254         self.rcs = [self._get_rc_name(rc) for rc in servers_cfg]
255         self.k8s_objs = [KubernetesObject(self._get_rc_name(rc),
256                                           ssh_key=self.ssh_key,
257                                           **cfg)
258                          for rc, cfg in servers_cfg.items()]
259         self.service_objs = [ServiceObject(s) for s in self.rcs]
260         self.crd = [CustomResourceDefinitionObject(self.name, **crd)
261                     for crd in crd_cfg]
262
263         self.pods = []
264
265     def _get_rc_name(self, rc_name):
266         return '{}-{}'.format(rc_name, self.name)
267
268     def get_rc_pods(self):
269         resp = k8s_utils.get_pod_list()
270         self.pods = [p.metadata.name for p in resp.items for s in self.rcs
271                      if p.metadata.name.startswith(s)]
272
273         return self.pods