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'
81 def __init__(self, name, **kwargs):
82 super(ReplicationControllerObject, self).__init__()
83 parameters = copy.deepcopy(kwargs)
85 self.node_selector = parameters.pop('nodeSelector', {})
86 self.ssh_key = parameters.pop('ssh_key', self.SSHKEY_DEFAULT)
87 self._volumes = parameters.pop('volumes', [])
88 self._security_context = parameters.pop('securityContext', None)
89 self._networks = parameters.pop('networks', [])
91 containers = parameters.pop('containers', None)
94 ContainerObject(self.name, self.ssh_key, **container)
95 for container in containers]
98 ContainerObject(self.name, self.ssh_key, **parameters)]
102 "kind": "ReplicationController",
110 "labels": {"app": name}
121 self._change_value_according_name(name)
122 self._add_containers()
123 self._add_node_selector()
125 self._add_security_context()
128 def get_template(self):
131 def _change_value_according_name(self, name):
132 utils.set_dict_value(self.template, 'metadata.name', name)
134 utils.set_dict_value(self.template,
135 'spec.template.metadata.labels.app',
138 def _add_containers(self):
139 containers = [container.get_container_item()
140 for container in self._containers]
141 utils.set_dict_value(self.template,
142 'spec.template.spec.containers',
145 def _add_node_selector(self):
146 utils.set_dict_value(self.template,
147 'spec.template.spec.nodeSelector',
150 def _add_volumes(self):
151 """Add "volume" items to container specs, including the SSH one"""
152 volume_items = [self._create_volume_item(vol) for vol in self._volumes]
153 volume_items.append(self._create_ssh_key_volume())
154 utils.set_dict_value(self.template,
155 'spec.template.spec.volumes',
158 def _create_ssh_key_volume(self):
159 """Create a "volume" item of type "configMap" for the SSH key"""
160 return {'name': self.ssh_key,
161 'configMap': {'name': self.ssh_key}}
164 def _create_volume_item(volume):
165 """Create a "volume" item"""
166 volume = copy.deepcopy(volume)
167 name = volume.pop('name')
168 for key in (k for k in volume if k in k8s_utils.get_volume_types()):
170 type_data = volume[key]
173 raise exceptions.KubernetesTemplateInvalidVolumeType(volume=volume)
175 return {'name': name,
176 type_name: type_data}
178 def _add_security_context(self):
179 if self._security_context:
180 utils.set_dict_value(self.template,
181 'spec.template.spec.securityContext',
182 self._security_context)
184 def _add_networks(self):
186 for net in self._networks:
187 networks.append({'name': net})
192 annotations = {'networks': jsonutils.dumps(networks)}
193 utils.set_dict_value(self.template,
194 'spec.template.metadata.annotations',
198 class ServiceNodePortObject(object):
200 def __init__(self, name, **kwargs):
201 """Service kind "NodePort" object
203 :param name: (string) name of the Service
204 :param kwargs: (dict) node_ports -> (list) port, name, targetPort,
207 self._name = '{}-service'.format(name)
209 'metadata': {'name': '{}-service'.format(name)},
213 'selector': {'app': name}
217 self._add_port(22, protocol='TCP')
218 node_ports = copy.deepcopy(kwargs.get('node_ports', []))
219 for port in node_ports:
220 port_number = port.pop('port')
221 self._add_port(port_number, **port)
223 def _add_port(self, port, protocol=None, name=None, targetPort=None,
225 _port = {'port': port}
227 _port['protocol'] = protocol
231 _port['targetPort'] = targetPort
233 _port['nodePort'] = nodePort
234 self.template['spec']['ports'].append(_port)
237 k8s_utils.create_service(self.template)
240 k8s_utils.delete_service(self._name)
243 class CustomResourceDefinitionObject(object):
245 MANDATORY_PARAMETERS = {'name'}
247 def __init__(self, ctx_name, **kwargs):
248 if not self.MANDATORY_PARAMETERS.issubset(kwargs):
249 missing_parameters = ', '.join(
250 str(param) for param in
251 (self.MANDATORY_PARAMETERS - set(kwargs)))
252 raise exceptions.KubernetesCRDObjectDefinitionError(
253 missing_parameters=missing_parameters)
255 singular = kwargs['name']
256 plural = singular + 's'
257 kind = singular.title()
258 version = kwargs.get('version', 'v1')
259 scope = kwargs.get('scope', constants.SCOPE_NAMESPACED)
260 group = ctx_name + '.com'
261 self._name = metadata_name = plural + '.' + group
265 'name': metadata_name
271 'names': {'plural': plural,
272 'singular': singular,
278 k8s_utils.create_custom_resource_definition(self._template)
281 k8s_utils.delete_custom_resource_definition(self._name)
284 class NetworkObject(object):
286 MANDATORY_PARAMETERS = {'name', 'plugin', 'args'}
289 def __init__(self, **kwargs):
290 if not self.MANDATORY_PARAMETERS.issubset(kwargs):
291 missing_parameters = ', '.join(
292 str(param) for param in
293 (self.MANDATORY_PARAMETERS - set(kwargs)))
294 raise exceptions.KubernetesNetworkObjectDefinitionError(
295 missing_parameters=missing_parameters)
297 self._name = kwargs['name']
298 self._plugin = kwargs['plugin']
299 self._args = kwargs['args']
301 self._template = None
311 crd = k8s_utils.get_custom_resource_definition(self.KIND)
313 raise exceptions.KubernetesNetworkObjectKindMissing()
321 self._group = self.crd.spec.group
328 self._version = self.crd.spec.version
335 self._plural = self.crd.spec.names.plural
342 self._scope = self.crd.spec.scope
347 """"Network" object template
349 This template can be rendered only once the CRD "Network" is created in
350 Kubernetes. This function call must be delayed until the creation of
354 return self._template
357 'apiVersion': '{}/{}'.format(self.group, self.version),
362 'plugin': self._plugin,
365 return self._template
368 k8s_utils.create_network(self.scope, self.group, self.version,
369 self.plural, self.template)
372 k8s_utils.delete_network(self.scope, self.group, self.version,
373 self.plural, self._name)
376 class KubernetesTemplate(object):
378 def __init__(self, name, context_cfg):
379 """KubernetesTemplate object initialization
381 :param name: (str) name of the Kubernetes context
382 :param context_cfg: (dict) context definition
384 context_cfg = copy.deepcopy(context_cfg)
385 servers_cfg = context_cfg.pop('servers', {})
386 crd_cfg = context_cfg.pop('custom_resources', [])
387 networks_cfg = context_cfg.pop('networks', [])
389 self.ssh_key = '{}-key'.format(name)
391 self.rcs = {self._get_rc_name(rc): cfg
392 for rc, cfg in servers_cfg.items()}
393 self.k8s_objs = [ReplicationControllerObject(
394 rc, ssh_key=self.ssh_key, **cfg) for rc, cfg in self.rcs.items()]
395 self.service_objs = [ServiceNodePortObject(rc, **cfg)
396 for rc, cfg in self.rcs.items()]
397 self.crd = [CustomResourceDefinitionObject(self.name, **crd)
399 self.network_objs = [NetworkObject(**nobj) for nobj in networks_cfg]
402 def _get_rc_name(self, rc_name):
403 return '{}-{}'.format(rc_name, self.name)
405 def get_rc_pods(self):
406 resp = k8s_utils.get_pod_list()
407 self.pods = [p.metadata.name for p in resp.items for s in self.rcs
408 if p.metadata.name.startswith(s)]