1 ##############################################################################
2 # Copyright (c) 2017 Huawei Technologies Co.,Ltd.
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 ##############################################################################
12 from oslo_serialization import jsonutils
14 from yardstick.common import constants
15 from yardstick.common import exceptions
16 from yardstick.common import kubernetes_utils as k8s_utils
17 from yardstick.common import utils
20 class ContainerObject(object):
22 SSH_MOUNT_PATH = '/tmp/.ssh/'
23 IMAGE_DEFAULT = 'openretriever/yardstick'
24 COMMAND_DEFAULT = '/bin/bash'
25 RESOURCES = ['requests', 'limits']
27 def __init__(self, name, ssh_key, **kwargs):
29 self._ssh_key = ssh_key
30 self._image = kwargs.get('image', self.IMAGE_DEFAULT)
31 self._command = [kwargs.get('command', self.COMMAND_DEFAULT)]
32 self._args = kwargs.get('args', [])
33 self._volume_mounts = kwargs.get('volumeMounts', [])
34 self._security_context = kwargs.get('securityContext')
35 self._env = kwargs.get('env', [])
36 self._resources = kwargs.get('resources', {})
38 def _create_volume_mounts(self):
39 """Return all "volumeMounts" items per container"""
40 volume_mounts_items = [self._create_volume_mounts_item(vol)
41 for vol in self._volume_mounts]
42 ssh_vol = {'name': self._ssh_key,
43 'mountPath': self.SSH_MOUNT_PATH}
44 volume_mounts_items.append(self._create_volume_mounts_item(ssh_vol))
45 return volume_mounts_items
48 def _create_volume_mounts_item(volume_mount):
49 """Create a "volumeMounts" item"""
50 return {'name': volume_mount['name'],
51 'mountPath': volume_mount['mountPath'],
52 'readOnly': volume_mount.get('readOnly', False)}
54 def get_container_item(self):
55 """Create a "container" item"""
56 container_name = '{}-container'.format(self._name)
57 container = {'args': self._args,
58 'command': self._command,
60 'name': container_name,
61 'volumeMounts': self._create_volume_mounts()}
62 if self._security_context:
63 container['securityContext'] = self._security_context
67 container['env'].append({'name': env['name'],
68 'value': env['value']})
70 container['resources'] = {}
71 for res in (res for res in self._resources if
72 res in self.RESOURCES):
73 container['resources'][res] = self._resources[res]
77 class ReplicationControllerObject(object):
79 SSHKEY_DEFAULT = 'yardstick_key'
80 RESTART_POLICY = ('Always', 'OnFailure', 'Never')
82 def __init__(self, name, **kwargs):
83 super(ReplicationControllerObject, self).__init__()
84 parameters = copy.deepcopy(kwargs)
86 self.node_selector = parameters.pop('nodeSelector', {})
87 self.ssh_key = parameters.pop('ssh_key', self.SSHKEY_DEFAULT)
88 self._volumes = parameters.pop('volumes', [])
89 self._security_context = parameters.pop('securityContext', None)
90 self._networks = parameters.pop('networks', [])
91 self._restart_policy = parameters.pop('restartPolicy', 'Always')
92 if self._restart_policy not in self.RESTART_POLICY:
93 raise exceptions.KubernetesWrongRestartPolicy(
94 rpolicy=self._restart_policy)
96 containers = parameters.pop('containers', None)
99 ContainerObject(self.name, self.ssh_key, **container)
100 for container in containers]
103 ContainerObject(self.name, self.ssh_key, **parameters)]
107 "kind": "ReplicationController",
115 "labels": {"app": name}
121 "restartPolicy": self._restart_policy
127 self._change_value_according_name(name)
128 self._add_containers()
129 self._add_node_selector()
131 self._add_security_context()
134 def get_template(self):
137 def _change_value_according_name(self, name):
138 utils.set_dict_value(self.template, 'metadata.name', name)
140 utils.set_dict_value(self.template,
141 'spec.template.metadata.labels.app',
144 def _add_containers(self):
145 containers = [container.get_container_item()
146 for container in self._containers]
147 utils.set_dict_value(self.template,
148 'spec.template.spec.containers',
151 def _add_node_selector(self):
152 utils.set_dict_value(self.template,
153 'spec.template.spec.nodeSelector',
156 def _add_volumes(self):
157 """Add "volume" items to container specs, including the SSH one"""
158 volume_items = [self._create_volume_item(vol) for vol in self._volumes]
159 volume_items.append(self._create_ssh_key_volume())
160 utils.set_dict_value(self.template,
161 'spec.template.spec.volumes',
164 def _create_ssh_key_volume(self):
165 """Create a "volume" item of type "configMap" for the SSH key"""
166 return {'name': self.ssh_key,
167 'configMap': {'name': self.ssh_key}}
170 def _create_volume_item(volume):
171 """Create a "volume" item"""
172 volume = copy.deepcopy(volume)
173 name = volume.pop('name')
174 for key in (k for k in volume if k in k8s_utils.get_volume_types()):
176 type_data = volume[key]
179 raise exceptions.KubernetesTemplateInvalidVolumeType(volume=volume)
181 return {'name': name,
182 type_name: type_data}
184 def _add_security_context(self):
185 if self._security_context:
186 utils.set_dict_value(self.template,
187 'spec.template.spec.securityContext',
188 self._security_context)
190 def _add_networks(self):
192 for net in self._networks:
193 networks.append({'name': net})
198 annotations = {'networks': jsonutils.dumps(networks)}
199 utils.set_dict_value(self.template,
200 'spec.template.metadata.annotations',
204 class ServiceNodePortObject(object):
206 def __init__(self, name, **kwargs):
207 """Service kind "NodePort" object
209 :param name: (string) name of the Service
210 :param kwargs: (dict) node_ports -> (list) port, name, targetPort,
213 self._name = '{}-service'.format(name)
215 'metadata': {'name': '{}-service'.format(name)},
219 'selector': {'app': name}
223 self._add_port(22, protocol='TCP')
224 node_ports = copy.deepcopy(kwargs.get('node_ports', []))
225 for port in node_ports:
226 port_number = port.pop('port')
227 self._add_port(port_number, **port)
229 def _add_port(self, port, protocol=None, name=None, targetPort=None,
231 _port = {'port': port}
233 _port['protocol'] = protocol
237 _port['targetPort'] = targetPort
239 _port['nodePort'] = nodePort
240 self.template['spec']['ports'].append(_port)
243 k8s_utils.create_service(self.template)
246 k8s_utils.delete_service(self._name)
249 class CustomResourceDefinitionObject(object):
251 MANDATORY_PARAMETERS = {'name'}
253 def __init__(self, ctx_name, **kwargs):
254 if not self.MANDATORY_PARAMETERS.issubset(kwargs):
255 missing_parameters = ', '.join(
256 str(param) for param in
257 (self.MANDATORY_PARAMETERS - set(kwargs)))
258 raise exceptions.KubernetesCRDObjectDefinitionError(
259 missing_parameters=missing_parameters)
261 singular = kwargs['name']
262 plural = singular + 's'
263 kind = singular.title()
264 version = kwargs.get('version', 'v1')
265 scope = kwargs.get('scope', constants.SCOPE_NAMESPACED)
266 group = ctx_name + '.com'
267 self._name = metadata_name = plural + '.' + group
271 'name': metadata_name
277 'names': {'plural': plural,
278 'singular': singular,
284 k8s_utils.create_custom_resource_definition(self._template)
287 k8s_utils.delete_custom_resource_definition(self._name)
290 class NetworkObject(object):
292 MANDATORY_PARAMETERS = {'name', 'plugin', 'args'}
295 def __init__(self, **kwargs):
296 if not self.MANDATORY_PARAMETERS.issubset(kwargs):
297 missing_parameters = ', '.join(
298 str(param) for param in
299 (self.MANDATORY_PARAMETERS - set(kwargs)))
300 raise exceptions.KubernetesNetworkObjectDefinitionError(
301 missing_parameters=missing_parameters)
303 self._name = kwargs['name']
304 self._plugin = kwargs['plugin']
305 self._args = kwargs['args']
307 self._template = None
317 crd = k8s_utils.get_custom_resource_definition(self.KIND)
319 raise exceptions.KubernetesNetworkObjectKindMissing()
327 self._group = self.crd.spec.group
334 self._version = self.crd.spec.version
341 self._plural = self.crd.spec.names.plural
348 self._scope = self.crd.spec.scope
353 """"Network" object template
355 This template can be rendered only once the CRD "Network" is created in
356 Kubernetes. This function call must be delayed until the creation of
360 return self._template
363 'apiVersion': '{}/{}'.format(self.group, self.version),
368 'plugin': self._plugin,
371 return self._template
374 k8s_utils.create_network(self.scope, self.group, self.version,
375 self.plural, self.template)
378 k8s_utils.delete_network(self.scope, self.group, self.version,
379 self.plural, self._name)
382 class KubernetesTemplate(object):
384 def __init__(self, name, context_cfg):
385 """KubernetesTemplate object initialization
387 :param name: (str) name of the Kubernetes context
388 :param context_cfg: (dict) context definition
390 context_cfg = copy.deepcopy(context_cfg)
391 servers_cfg = context_cfg.pop('servers', {})
392 crd_cfg = context_cfg.pop('custom_resources', [])
393 networks_cfg = context_cfg.pop('networks', [])
395 self.ssh_key = '{}-key'.format(name)
397 self.rcs = {self._get_rc_name(rc): cfg
398 for rc, cfg in servers_cfg.items()}
399 self.k8s_objs = [ReplicationControllerObject(
400 rc, ssh_key=self.ssh_key, **cfg) for rc, cfg in self.rcs.items()]
401 self.service_objs = [ServiceNodePortObject(rc, **cfg)
402 for rc, cfg in self.rcs.items()]
403 self.crd = [CustomResourceDefinitionObject(self.name, **crd)
405 self.network_objs = [NetworkObject(**nobj) for nobj in networks_cfg]
408 def _get_rc_name(self, rc_name):
409 return '{}-{}'.format(rc_name, self.name)
411 def get_rc_pods(self):
412 resp = k8s_utils.get_pod_list()
413 self.pods = [p.metadata.name for p in resp.items for s in self.rcs
414 if p.metadata.name.startswith(s)]