CSIT-748 Add K8S 2memif-2vnf topologies
[csit.git] / resources / libraries / python / KubernetesUtils.py
1 # Copyright (c) 2017 Cisco and/or its affiliates.
2 # Licensed under the Apache License, Version 2.0 (the "License");
3 # you may not use this file except in compliance with the License.
4 # You may obtain a copy of the License at:
5 #
6 #     http://www.apache.org/licenses/LICENSE-2.0
7 #
8 # Unless required by applicable law or agreed to in writing, software
9 # distributed under the License is distributed on an "AS IS" BASIS,
10 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11 # See the License for the specific language governing permissions and
12 # limitations under the License.
13
14 """Library to control Kubernetes kubectl."""
15
16 import time
17 import yaml
18
19 from resources.libraries.python.constants import Constants
20 from resources.libraries.python.topology import NodeType
21 from resources.libraries.python.ssh import SSH
22 from resources.libraries.python.CpuUtils import CpuUtils
23 from resources.libraries.python.VppConfigGenerator import VppConfigGenerator
24
25 __all__ = ["KubernetesUtils"]
26
27
28 class KubernetesUtils(object):
29     """Kubernetes utilities class."""
30
31     def __init__(self):
32         """Initialize KubernetesUtils class."""
33         pass
34
35     @staticmethod
36     def setup_kubernetes_on_node(node):
37         """Set up Kubernetes on node.
38
39         :param node: DUT node.
40         :type node: dict
41         :raises RuntimeError: If Kubernetes setup failed on node.
42         """
43         ssh = SSH()
44         ssh.connect(node)
45
46         cmd = '{dir}/{lib}/k8s_setup.sh '.format(dir=Constants.REMOTE_FW_DIR,
47                                                  lib=Constants.RESOURCES_LIB_SH)
48         (ret_code, _, _) = ssh.exec_command(cmd, timeout=120)
49         if int(ret_code) != 0:
50             raise RuntimeError('Failed to setup Kubernetes on {node}.'
51                                .format(node=node['host']))
52
53         KubernetesUtils.wait_for_kubernetes_pods_on_node(node,
54                                                          nspace='kube-system')
55
56     @staticmethod
57     def setup_kubernetes_on_all_duts(nodes):
58         """Set up Kubernetes on all DUTs.
59
60         :param nodes: Topology nodes.
61         :type nodes: dict
62         """
63         for node in nodes.values():
64             if node['type'] == NodeType.DUT:
65                 KubernetesUtils.setup_kubernetes_on_node(node)
66
67     @staticmethod
68     def apply_kubernetes_resource_on_node(node, yaml_file, **kwargs):
69         """Apply Kubernetes resource on node.
70
71         :param node: DUT node.
72         :param yaml_file: YAML configuration file.
73         :param kwargs: Key-value pairs to replace in YAML template.
74         :type node: dict
75         :type yaml_file: str
76         :type kwargs: dict
77         :raises RuntimeError: If applying Kubernetes template failed.
78         """
79         ssh = SSH()
80         ssh.connect(node)
81
82         stream = file('{tpl}/{yaml}'.format(tpl=Constants.RESOURCES_TPL_K8S,
83                                             yaml=yaml_file), 'r')
84
85         for data in yaml.load_all(stream):
86             data = reduce(lambda a, kv: a.replace(*kv), kwargs.iteritems(),
87                           yaml.dump(data, default_flow_style=False))
88             # Workaround to avoid using RAW string anotated with | in YAML as
89             # library + bash is misinterpreting spaces.
90             data = data.replace('.conf:\n', '.conf: |\n')
91             cmd = 'cat <<EOF | kubectl apply -f - \n{data}\nEOF'.format(
92                 data=data)
93             (ret_code, _, _) = ssh.exec_command_sudo(cmd, timeout=120)
94             if int(ret_code) != 0:
95                 raise RuntimeError('Failed to apply Kubernetes template {yaml} '
96                                    'on {node}.'.format(yaml=yaml_file,
97                                                        node=node['host']))
98
99     @staticmethod
100     def apply_kubernetes_resource_on_all_duts(nodes, yaml_file, **kwargs):
101         """Apply Kubernetes resource on all DUTs.
102
103         :param nodes: Topology nodes.
104         :param yaml_file: YAML configuration file.
105         :param kwargs: Key-value pairs to replace in YAML template.
106         :type nodes: dict
107         :type yaml_file: str
108         :type kwargs: dict
109         """
110         for node in nodes.values():
111             if node['type'] == NodeType.DUT:
112                 KubernetesUtils.apply_kubernetes_resource_on_node(node,
113                                                                   yaml_file,
114                                                                   **kwargs)
115
116     @staticmethod
117     def create_kubernetes_cm_from_file_on_node(node, name, key, src_file):
118         """Create Kubernetes ConfigMap from file on node.
119
120         :param node: DUT node.
121         :param name: ConfigMap name.
122         :param key: Key (destination file).
123         :param src_file: Source file.
124         :type node: dict
125         :type name: str
126         :type key: str
127         :type src_file: str
128         :raises RuntimeError: If creating Kubernetes ConfigMap failed.
129         """
130         ssh = SSH()
131         ssh.connect(node)
132
133         cmd = 'kubectl create -n csit configmap {name} --from-file={key}='\
134             '{src_file}'.format(name=name, key=key, src_file=src_file)
135         (ret_code, _, _) = ssh.exec_command_sudo(cmd, timeout=120)
136         if int(ret_code) != 0:
137             raise RuntimeError('Failed to create Kubernetes ConfigMap {name} '
138                                'on {node}.'.format(name=name,
139                                                    node=node['host']))
140
141     @staticmethod
142     def create_kubernetes_cm_from_file_on_all_duts(nodes, name, key, src_file):
143         """Create Kubernetes ConfigMap from file on all DUTs.
144
145         :param nodes: Topology nodes.
146         :param name: ConfigMap name.
147         :param key: Key (destination file).
148         :param src_file: Source file.
149         :type nodes: dict
150         :type name: str
151         :type key: str
152         :type src_file: str
153         """
154         for node in nodes.values():
155             if node['type'] == NodeType.DUT:
156                 KubernetesUtils.create_kubernetes_cm_from_file_on_node(node,
157                                                                        name,
158                                                                        key,
159                                                                        src_file)
160
161     @staticmethod
162     def delete_kubernetes_resource_on_node(node, rtype='po,cm', name=None):
163         """Delete Kubernetes resource on node.
164
165         :param node: DUT node.
166         :param rtype: Kubernetes resource type.
167         :param name: Name of resource.
168         :type node: dict
169         :type rtype: str
170         :type name: str
171         :raises RuntimeError: If deleting Kubernetes resource failed.
172         """
173         ssh = SSH()
174         ssh.connect(node)
175
176         name = '{name}'.format(name=name) if name else '--all'
177
178         cmd = 'kubectl delete -n csit {rtype} {name}'\
179             .format(rtype=rtype, name=name)
180         (ret_code, _, _) = ssh.exec_command_sudo(cmd, timeout=120)
181         if int(ret_code) != 0:
182             raise RuntimeError('Failed to delete Kubernetes resources in CSIT '
183                                'namespace on {node}.'.format(node=node['host']))
184
185         cmd = 'kubectl get -n csit pods --no-headers'
186         for _ in range(24):
187             (ret_code, stdout, _) = ssh.exec_command_sudo(cmd, timeout=120)
188             if int(ret_code) == 0:
189                 ready = True
190                 for line in stdout.splitlines():
191                     if 'No resources found.' not in line:
192                         ready = False
193                 if ready:
194                     break
195             time.sleep(5)
196         else:
197             raise RuntimeError('Failed to delete Kubernetes resources in CSIT '
198                                'namespace on {node}.'.format(node=node['host']))
199
200     @staticmethod
201     def delete_kubernetes_resource_on_all_duts(nodes, rtype='po,cm', name=None):
202         """Delete all Kubernetes resource on all DUTs.
203
204         :param nodes: Topology nodes.
205         :param rtype: Kubernetes resource type.
206         :param name: Name of resource.
207         :type nodes: dict
208         :type rtype: str
209         :type name: str
210         """
211         for node in nodes.values():
212             if node['type'] == NodeType.DUT:
213                 KubernetesUtils.delete_kubernetes_resource_on_node(node, rtype,
214                                                                    name)
215
216     @staticmethod
217     def describe_kubernetes_resource_on_node(node, rtype='po,cm'):
218         """Describe Kubernetes resource on node.
219
220         :param node: DUT node.
221         :param rtype: Kubernetes resource type.
222         :type node: dict
223         :type rtype: str
224         :raises RuntimeError: If describing Kubernetes resource failed.
225         """
226         ssh = SSH()
227         ssh.connect(node)
228
229         cmd = 'kubectl describe -n csit {rtype}'.format(rtype=rtype)
230         (ret_code, _, _) = ssh.exec_command_sudo(cmd, timeout=120)
231         if int(ret_code) != 0:
232             raise RuntimeError('Failed to describe Kubernetes resource on '
233                                '{node}.'.format(node=node['host']))
234
235     @staticmethod
236     def describe_kubernetes_resource_on_all_duts(nodes, rtype='po,cm'):
237         """Describe Kubernetes resource on all DUTs.
238
239         :param nodes: Topology nodes.
240         :param rtype: Kubernetes resource type.
241         :type nodes: dict
242         :type rtype: str
243         """
244         for node in nodes.values():
245             if node['type'] == NodeType.DUT:
246                 KubernetesUtils.describe_kubernetes_resource_on_node(node,
247                                                                      rtype)
248
249     @staticmethod
250     def get_kubernetes_logs_on_node(node, nspace='csit'):
251         """Get Kubernetes logs on node.
252
253         :param node: DUT node.
254         :param nspace: Kubernetes namespace.
255         :type node: dict
256         :type nspace: str
257         """
258         ssh = SSH()
259         ssh.connect(node)
260
261         cmd = "for p in $(kubectl get pods -n {namespace} --no-headers"\
262             " | cut -f 1 -d ' '); do echo $p; kubectl logs -n {namespace} $p; "\
263             "done".format(namespace=nspace)
264         ssh.exec_command(cmd, timeout=120)
265
266     @staticmethod
267     def get_kubernetes_logs_on_all_duts(nodes, nspace='csit'):
268         """Get Kubernetes logs on all DUTs.
269
270         :param nodes: Topology nodes.
271         :param nspace: Kubernetes namespace.
272         :type nodes: dict
273         :type nspace: str
274         """
275         for node in nodes.values():
276             if node['type'] == NodeType.DUT:
277                 KubernetesUtils.get_kubernetes_logs_on_node(node, nspace)
278
279     @staticmethod
280     def reset_kubernetes_on_node(node):
281         """Reset Kubernetes on node.
282
283         :param node: DUT node.
284         :type node: dict
285         :raises RuntimeError: If resetting Kubernetes failed.
286         """
287         ssh = SSH()
288         ssh.connect(node)
289
290         cmd = 'kubeadm reset && rm -rf $HOME/.kube'
291         (ret_code, _, _) = ssh.exec_command_sudo(cmd, timeout=120)
292         if int(ret_code) != 0:
293             raise RuntimeError('Failed to reset Kubernetes on {node}.'
294                                .format(node=node['host']))
295
296     @staticmethod
297     def reset_kubernetes_on_all_duts(nodes):
298         """Reset Kubernetes on all DUTs.
299
300         :param nodes: Topology nodes.
301         :type nodes: dict
302         """
303         for node in nodes.values():
304             if node['type'] == NodeType.DUT:
305                 KubernetesUtils.reset_kubernetes_on_node(node)
306
307     @staticmethod
308     def wait_for_kubernetes_pods_on_node(node, nspace='csit'):
309         """Wait for Kubernetes PODs to become in 'Running' state on node.
310
311         :param node: DUT node.
312         :param nspace: Kubernetes namespace.
313         :type node: dict
314         :type nspace: str
315         :raises RuntimeError: If Kubernetes PODs are not ready.
316         """
317         ssh = SSH()
318         ssh.connect(node)
319
320         cmd = 'kubectl get -n {namespace} pods --no-headers'\
321             .format(namespace=nspace)
322         for _ in range(48):
323             (ret_code, stdout, _) = ssh.exec_command_sudo(cmd, timeout=120)
324             if int(ret_code) == 0:
325                 ready = False
326                 for line in stdout.splitlines():
327                     try:
328                         state = line.split()[1].split('/')
329                         ready = True if 'Running' in line and\
330                             state == state[::-1] else False
331                         if not ready:
332                             break
333                     except ValueError, IndexError:
334                         ready = False
335                 if ready:
336                     break
337             time.sleep(5)
338         else:
339             raise RuntimeError('Kubernetes PODs are not ready on {node}.'
340                                .format(node=node['host']))
341
342     @staticmethod
343     def wait_for_kubernetes_pods_on_all_duts(nodes, nspace='csit'):
344         """Wait for Kubernetes PODs to become in Running state on all DUTs.
345
346         :param nodes: Topology nodes.
347         :param nspace: Kubernetes namespace.
348         :type nodes: dict
349         :type nspace: str
350         """
351         for node in nodes.values():
352             if node['type'] == NodeType.DUT:
353                 KubernetesUtils.wait_for_kubernetes_pods_on_node(node, nspace)
354
355     @staticmethod
356     def create_kubernetes_vswitch_startup_config(**kwargs):
357         """Create Kubernetes VSWITCH startup configuration.
358
359         :param kwargs: Key-value pairs used to create configuration.
360         :param kwargs: dict
361         """
362         cpuset_cpus = \
363             CpuUtils.cpu_slice_of_list_per_node(node=kwargs['node'],
364                                                 cpu_node=kwargs['cpu_node'],
365                                                 skip_cnt=kwargs['cpu_skip'],
366                                                 cpu_cnt=kwargs['cpu_cnt'],
367                                                 smt_used=kwargs['smt_used'])
368
369         # Create config instance
370         vpp_config = VppConfigGenerator()
371         vpp_config.set_node(kwargs['node'])
372         vpp_config.add_unix_cli_listen(value='0.0.0.0:5002')
373         vpp_config.add_unix_nodaemon()
374         vpp_config.add_dpdk_socketmem('1024,1024')
375         vpp_config.add_heapsize('3G')
376         vpp_config.add_ip6_hash_buckets('2000000')
377         vpp_config.add_ip6_heap_size('3G')
378         if kwargs['framesize'] < 1522:
379             vpp_config.add_dpdk_no_multi_seg()
380         vpp_config.add_dpdk_dev_default_rxq(kwargs['rxq'])
381         vpp_config.add_dpdk_dev(kwargs['if1'], kwargs['if2'])
382         # We will pop first core from list to be main core
383         vpp_config.add_cpu_main_core(str(cpuset_cpus.pop(0)))
384         # if this is not only core in list, the rest will be used as workers.
385         if cpuset_cpus:
386             corelist_workers = ','.join(str(cpu) for cpu in cpuset_cpus)
387             vpp_config.add_cpu_corelist_workers(corelist_workers)
388         vpp_config.apply_config(filename=kwargs['filename'], restart_vpp=False)
389
390     @staticmethod
391     def create_kubernetes_vnf_startup_config(**kwargs):
392         """Create Kubernetes VNF startup configuration.
393
394         :param kwargs: Key-value pairs used to create configuration.
395         :param kwargs: dict
396         """
397         skip_cnt = kwargs['cpu_skip'] + (kwargs['i'] - 1) * kwargs['cpu_cnt']
398         cpuset_cpus = \
399             CpuUtils.cpu_slice_of_list_per_node(node=kwargs['node'],
400                                                 cpu_node=kwargs['cpu_node'],
401                                                 skip_cnt=skip_cnt,
402                                                 cpu_cnt=kwargs['cpu_cnt'],
403                                                 smt_used=kwargs['smt_used'])
404
405         # Create config instance
406         vpp_config = VppConfigGenerator()
407         vpp_config.set_node(kwargs['node'])
408         vpp_config.add_unix_cli_listen(value='0.0.0.0:5002')
409         vpp_config.add_unix_nodaemon()
410         # We will pop first core from list to be main core
411         vpp_config.add_cpu_main_core(str(cpuset_cpus.pop(0)))
412         # if this is not only core in list, the rest will be used as workers.
413         if cpuset_cpus:
414             corelist_workers = ','.join(str(cpu) for cpu in cpuset_cpus)
415             vpp_config.add_cpu_corelist_workers(corelist_workers)
416         vpp_config.add_plugin_disable('dpdk_plugin.so')
417         vpp_config.apply_config(filename=kwargs['filename'], restart_vpp=False)