blob: 02dc2f68ee2b8bf9bc412c4880f4211ea65a45a7 [file] [log] [blame]
Artem Panchenko0594cd72017-06-12 13:25:26 +03001# Copyright 2017 Mirantis, Inc.
2#
3# Licensed under the Apache License, Version 2.0 (the "License"); you may
4# not use this file except in compliance with the License. You may obtain
5# a copy of the License at
6#
7# http://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
11# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
12# License for the specific language governing permissions and limitations
13# under the License.
14
Victor Ryzhenkin8ff3c3f2018-01-17 19:37:05 +040015import os
Vladimir Jigulina6b018b2018-07-18 15:19:01 +040016import requests
Vladimir Jigulin0c8dd5a2018-08-28 05:08:35 +040017import yaml
Artem Panchenko0594cd72017-06-12 13:25:26 +030018
19from devops.helpers import helpers
Victor Ryzhenkin66d39372017-09-28 19:25:48 +040020from devops.error import DevopsCalledProcessError
Artem Panchenko0594cd72017-06-12 13:25:26 +030021
22from tcp_tests import logger
Victor Ryzhenkin66d39372017-09-28 19:25:48 +040023from tcp_tests.helpers import ext
24from tcp_tests.helpers.utils import retry
Artem Panchenko0594cd72017-06-12 13:25:26 +030025from tcp_tests.managers.execute_commands import ExecuteCommandsMixin
26from tcp_tests.managers.k8s import cluster
Artem Panchenko0594cd72017-06-12 13:25:26 +030027
28LOG = logger.logger
29
30
31class K8SManager(ExecuteCommandsMixin):
32 """docstring for K8SManager"""
33
34 __config = None
35 __underlay = None
36
37 def __init__(self, config, underlay, salt):
38 self.__config = config
39 self.__underlay = underlay
40 self._salt = salt
Vladimir Jigulin4ad52a82018-08-12 05:51:30 +040041 self._api = None
42 self.kubectl = K8SKubectlCli(self)
43 self.virtlet = K8SVirtlet(self)
Victor Ryzhenkine784bbf2018-12-21 03:58:00 +040044 self.conformance_node = None
Vladimir Jigulin4ad52a82018-08-12 05:51:30 +040045 super(K8SManager, self).__init__(config=config, underlay=underlay)
Artem Panchenko0594cd72017-06-12 13:25:26 +030046
47 def install(self, commands):
48 self.execute_commands(commands,
49 label='Install Kubernetes services')
50 self.__config.k8s.k8s_installed = True
51 self.__config.k8s.kube_host = self.get_proxy_api()
52
53 def get_proxy_api(self):
54 k8s_proxy_ip_pillars = self._salt.get_pillar(
vrovachev99228d32017-06-08 19:46:10 +040055 tgt='I@haproxy:proxy:enabled:true and I@kubernetes:master',
Artem Panchenko0594cd72017-06-12 13:25:26 +030056 pillar='haproxy:proxy:listen:k8s_secure:binds:address')
vrovachev99228d32017-06-08 19:46:10 +040057 k8s_hosts = self._salt.get_pillar(
58 tgt='I@haproxy:proxy:enabled:true and I@kubernetes:master',
59 pillar='kubernetes:pool:apiserver:host')
Artem Panchenko0594cd72017-06-12 13:25:26 +030060 k8s_proxy_ip = set([ip
61 for item in k8s_proxy_ip_pillars
Dina Belovae6fdffb2017-09-19 13:58:34 -070062 for node, ip in item.items() if ip])
vrovachev99228d32017-06-08 19:46:10 +040063 k8s_hosts = set([ip
Dina Belovae6fdffb2017-09-19 13:58:34 -070064 for item in k8s_hosts
65 for node, ip in item.items() if ip])
vrovachev99228d32017-06-08 19:46:10 +040066 assert len(k8s_hosts) == 1, (
67 "Found more than one Kubernetes API hosts in pillars:{0}, "
68 "expected one!").format(k8s_hosts)
69 k8s_host = k8s_hosts.pop()
70 assert k8s_host in k8s_proxy_ip, (
71 "Kubernetes API host:{0} not found in proxies:{} "
72 "on k8s master nodes. K8s proxies are expected on "
73 "nodes with K8s master").format(k8s_host, k8s_proxy_ip)
74 return k8s_host
Artem Panchenko0594cd72017-06-12 13:25:26 +030075
Vladimir Jigulin4ad52a82018-08-12 05:51:30 +040076 def _api_init(self):
77 ca_result = self.controller_check_call(
78 'base64 --wrap=0 /etc/kubernetes/ssl/ca-kubernetes.crt')
79
80 self._api = cluster.K8sCluster(
81 user=self.__config.k8s_deploy.kubernetes_admin_user,
82 password=self.__config.k8s_deploy.kubernetes_admin_password,
83 ca=ca_result['stdout'][0],
84 host=self.__config.k8s.kube_host,
85 port=self.__config.k8s.kube_apiserver_port)
86
Artem Panchenko0594cd72017-06-12 13:25:26 +030087 @property
88 def api(self):
Vladimir Jigulin4ad52a82018-08-12 05:51:30 +040089 """
90 :rtype: cluster.K8sCluster
91 """
92 if self._api is None:
93 self._api_init()
94 return self._api
Artem Panchenko0594cd72017-06-12 13:25:26 +030095
Vladimir Jigulin4ad52a82018-08-12 05:51:30 +040096 def get_controllers(self):
97 """ Return list of controllers ssh underlays """
Vladimir Jigulin34dfa942018-07-23 21:05:48 +040098 return [node for node in self.__config.underlay.ssh if
99 ext.UNDERLAY_NODE_ROLES.k8s_controller in node['roles']]
100
Vladimir Jigulin4ad52a82018-08-12 05:51:30 +0400101 def get_masters(self):
102 """ Return list of kubernetes masters hosts fqdn """
103 masters_fqdn = self._salt.get_pillar(
104 tgt='I@kubernetes:master', pillar='linux:network:fqdn')
105 return [self.__underlay.host_by_node_name(node_name=v)
106 for pillar in masters_fqdn for k, v in pillar.items()]
107
abaraniukbef60d92019-01-14 12:37:14 +0200108 def get_masters_name(self):
109 """ Return list of kubernetes masters hosts fqdn """
110 masters_fqdn = self._salt.get_pillar(
111 tgt='I@kubernetes:master', pillar='linux:network:fqdn')
112 return [v for pillar in masters_fqdn for k, v in pillar.items()]
113
Victor Ryzhenkin66d39372017-09-28 19:25:48 +0400114 @property
Vladimir Jigulin4ad52a82018-08-12 05:51:30 +0400115 def controller_name(self):
116 """ Return node name of controller node that used for all actions """
abaraniukbef60d92019-01-14 12:37:14 +0200117 names = [node for node in self.get_masters_name()]
Vladimir Jigulin4ad52a82018-08-12 05:51:30 +0400118 # we want to return same controller name every time
119 names.sort()
120 return names[0]
Victor Ryzhenkin66d39372017-09-28 19:25:48 +0400121
Dennis Dmitriev66650fc2018-11-02 11:04:37 +0200122 @property
123 def controller_minion_id(self):
124 """ Return node name of controller node that used for all actions """
125 minion_ids = [minion_id['minion_id'] for minion_id in
126 self.get_controllers()]
127 # we want to return same controller name every time
128 minion_ids.sort()
129 return minion_ids[0]
130
131 @property
132 def is_metallb_enabled(self):
133 ctl_tgt = self.controller_minion_id
134 LOG.debug("Controller target: {}".format(ctl_tgt))
135
136 result = self._salt.get_pillar(
137 tgt=ctl_tgt,
138 pillar='kubernetes:common:addons:metallb:enabled')
139 metallb = result[0].get(ctl_tgt, False)
140 LOG.info("{} kubernetes:common:addons:metallb:enabled: {}"
141 .format(ctl_tgt, bool(metallb)))
142 return metallb
143
144 @property
145 def is_ingress_nginx_enabled(self):
146 ctl_tgt = self.controller_minion_id
147 LOG.debug("Controller target: {}".format(ctl_tgt))
148
149 result = self._salt.get_pillar(
150 tgt=ctl_tgt,
151 pillar='kubernetes:common:addons:ingress-nginx:enabled')
152 ingress_nginx = result[0].get(ctl_tgt, False)
153 LOG.info("{} kubernetes:common:addons:ingress-nginx:enabled: {}"
154 .format(ctl_tgt, bool(ingress_nginx)))
155 return ingress_nginx
156
Vladimir Jigulin4ad52a82018-08-12 05:51:30 +0400157 def controller_check_call(self, cmd, **kwargs):
158 """ Run command on controller and return result """
159 LOG.info("running cmd on k8s controller: {}".format(cmd))
Vladimir Jigulinee1faa52018-06-25 13:00:51 +0400160 return self.__underlay.check_call(
Vladimir Jigulin4ad52a82018-08-12 05:51:30 +0400161 cmd=cmd, node_name=self.controller_name, **kwargs)
Artem Panchenko501e67e2017-06-14 14:59:18 +0300162
Vladimir Jigulin4ad52a82018-08-12 05:51:30 +0400163 def get_keepalived_vip(self):
Vladimir Jigulin62bcf462018-05-28 18:17:01 +0400164 """
Vladimir Jigulin4ad52a82018-08-12 05:51:30 +0400165 Return k8s VIP IP address
Vladimir Jigulin62bcf462018-05-28 18:17:01 +0400166
Vladimir Jigulin4ad52a82018-08-12 05:51:30 +0400167 :return: str, IP address
Vladimir Jigulin62bcf462018-05-28 18:17:01 +0400168 """
Vladimir Jigulin4ad52a82018-08-12 05:51:30 +0400169 ctl_vip_pillar = self._salt.get_pillar(
Vladimir Jigulinc69f4ba2019-01-14 15:27:44 +0400170 tgt="I@kubernetes:control",
Vladimir Jigulin4ad52a82018-08-12 05:51:30 +0400171 pillar="_param:cluster_vip_address")[0]
172 return ctl_vip_pillar.values()[0]
Vladimir Jigulin62bcf462018-05-28 18:17:01 +0400173
Vladimir Jigulin4ad52a82018-08-12 05:51:30 +0400174 def run_sample_deployment(self, name, **kwargs):
175 return K8SSampleDeployment(self, name, **kwargs)
Victor Ryzhenkin3ffa2b42017-10-05 16:38:44 +0400176
Vladimir Jigulin4ad52a82018-08-12 05:51:30 +0400177 def get_pod_ips_from_container(self, pod_name, exclude_local=True,
178 namespace='default'):
179 """ Get ips from container using 'ip a'
180 Required for cni-genie multi-cni cases
181
182 :return: list of IP adresses
Victor Ryzhenkin3ffa2b42017-10-05 16:38:44 +0400183 """
Vladimir Jigulin4ad52a82018-08-12 05:51:30 +0400184 cmd = "ip a|grep \"inet \"|awk '{{print $2}}'"
185 result = self.kubectl.cli_exec(namespace, pod_name, cmd)['stdout']
186 ips = [line.strip().split('/')[0] for line in result]
187 if exclude_local:
188 ips = [ip for ip in ips if not ip.startswith("127.")]
189 return ips
Victor Ryzhenkin3ffa2b42017-10-05 16:38:44 +0400190
Vladimir Jigulin4ad52a82018-08-12 05:51:30 +0400191 def update_k8s_version(self, tag):
Vladimir Jigulin62bcf462018-05-28 18:17:01 +0400192 """
193 Update k8s images tag version in cluster meta and apply required
194 for update states
195
196 :param tag: New version tag of k8s images
197 :return:
198 """
199 master_host = self.__config.salt.salt_master_host
200
201 def update_image_tag_meta(config, image_name):
202 image_old = config.get(image_name)
203 image_base = image_old.split(':')[0]
204 image_new = "{}:{}".format(image_base, tag)
205 LOG.info("Changing k8s '{0}' image cluster meta to '{1}'".format(
206 image_name, image_new))
207
208 with self.__underlay.remote(host=master_host) as r:
209 cmd = "salt-call reclass.cluster_meta_set" \
210 " name={0} value={1}".format(image_name, image_new)
211 r.check_call(cmd)
212 return image_new
213
214 cfg = self.__config
215
216 update_image_tag_meta(cfg.k8s_deploy, "kubernetes_hyperkube_image")
217 update_image_tag_meta(cfg.k8s_deploy, "kubernetes_pause_image")
218 cfg.k8s.k8s_conformance_image = update_image_tag_meta(
219 cfg.k8s, "k8s_conformance_image")
220
221 steps_path = cfg.k8s_deploy.k8s_update_steps_path
222 update_commands = self.__underlay.read_template(steps_path)
223 self.execute_commands(
224 update_commands, label="Updating kubernetes to '{}'".format(tag))
Vladimir Jigulinee1faa52018-06-25 13:00:51 +0400225
Vladimir Jigulin4ad52a82018-08-12 05:51:30 +0400226 def run_conformance(self, timeout=60*60, log_out='k8s_conformance.log',
227 raise_on_err=True, node_name=None,
228 api_server='http://127.0.0.1:8080'):
229 if node_name is None:
230 node_name = self.controller_name
231 cmd = "set -o pipefail; docker run --net=host " \
232 "-e API_SERVER='{api}' {image} | tee '{log}'".format(
233 api=api_server, log=log_out,
234 image=self.__config.k8s.k8s_conformance_image)
235 return self.__underlay.check_call(
236 cmd=cmd, node_name=node_name, timeout=timeout,
Dennis Dmitrieveb50ce12018-09-27 13:34:32 +0300237 raise_on_err=raise_on_err, verbose=True)
Vladimir Jigulin4ad52a82018-08-12 05:51:30 +0400238
239 def run_virtlet_conformance(self, timeout=60 * 120,
Dennis Dmitriev34fd3002018-11-15 18:25:16 +0200240 log_file='virtlet_conformance.log',
241 report_name="report.xml"):
Vladimir Jigulin4ad52a82018-08-12 05:51:30 +0400242 if self.__config.k8s.run_extended_virtlet_conformance:
243 ci_image = "cloud-images.ubuntu.com/xenial/current/" \
244 "xenial-server-cloudimg-amd64-disk1.img"
245 cmd = ("set -o pipefail; "
246 "docker run --net=host {0} /virtlet-e2e-tests "
Dennis Dmitriev34fd3002018-11-15 18:25:16 +0200247 "-include-cloud-init-tests -junitOutput {3} "
Vladimir Jigulin4ad52a82018-08-12 05:51:30 +0400248 "-image {2} -sshuser ubuntu -memoryLimit 1024 "
249 "-alsologtostderr -cluster-url http://127.0.0.1:8080 "
250 "-ginkgo.focus '\[Conformance\]' "
251 "| tee {1}".format(
252 self.__config.k8s_deploy.kubernetes_virtlet_image,
Dennis Dmitriev34fd3002018-11-15 18:25:16 +0200253 log_file, ci_image, report_name))
Vladimir Jigulin4ad52a82018-08-12 05:51:30 +0400254 else:
255 cmd = ("set -o pipefail; "
256 "docker run --net=host {0} /virtlet-e2e-tests "
Dennis Dmitriev34fd3002018-11-15 18:25:16 +0200257 "-junitOutput {2} "
Vladimir Jigulin4ad52a82018-08-12 05:51:30 +0400258 "-alsologtostderr -cluster-url http://127.0.0.1:8080 "
259 "-ginkgo.focus '\[Conformance\]' "
260 "| tee {1}".format(
261 self.__config.k8s_deploy.kubernetes_virtlet_image,
Dennis Dmitriev34fd3002018-11-15 18:25:16 +0200262 log_file, report_name))
Vladimir Jigulin4ad52a82018-08-12 05:51:30 +0400263 LOG.info("Executing: {}".format(cmd))
264 with self.__underlay.remote(
265 node_name=self.controller_name) as remote:
266 result = remote.check_call(cmd, timeout=timeout)
267 stderr = result['stderr']
268 stdout = result['stdout']
269 LOG.info("Test results stdout: {}".format(stdout))
270 LOG.info("Test results stderr: {}".format(stderr))
271 return result
272
Vladimir Jigulin51611672018-11-23 03:10:22 +0400273 def start_k8s_cncf_verification(self, timeout=60 * 180):
Vladimir Jigulin0c8dd5a2018-08-28 05:08:35 +0400274 """
275 Build sonobuoy using golang docker image and install it in system
276 Then generate sonobuoy verification manifest using gen command
277 and wait for it to end using status annotation
278 TODO (vjigulin): All sonobuoy methods can be improved if we define
279 correct KUBECONFIG env variable
280 """
281 cncf_cmd =\
282 'docker run --network host --name sonobuoy golang:1.11 bash -c ' \
283 '"go get -d -v github.com/heptio/sonobuoy && ' \
284 'go install -v github.com/heptio/sonobuoy" && ' \
285 'docker cp sonobuoy:/go/bin/sonobuoy /usr/local/bin/ && ' \
286 'docker rm sonobuoy && ' \
287 'sonobuoy gen | kubectl apply -f -'
Vladimir Jigulin4ad52a82018-08-12 05:51:30 +0400288
Vladimir Jigulin0c8dd5a2018-08-28 05:08:35 +0400289 self.controller_check_call(cncf_cmd, timeout=900)
290
291 sonobuoy_pod = self.api.pods.get('sonobuoy', 'heptio-sonobuoy')
292 sonobuoy_pod.wait_running()
293
294 def sonobuoy_status():
295 annotations = sonobuoy_pod.read().metadata.annotations
296 json_status = annotations['sonobuoy.hept.io/status']
297 status = yaml.safe_load(json_status)['status']
298 if status != 'running':
299 LOG.info("CNCF status: {}".format(json_status))
300 return yaml.safe_load(json_status)['status']
301
302 LOG.info("Waiting for CNCF to complete")
303 helpers.wait(
304 lambda: sonobuoy_status() == 'complete',
Vladimir Jigulin51611672018-11-23 03:10:22 +0400305 interval=120, timeout=timeout,
Vladimir Jigulin0c8dd5a2018-08-28 05:08:35 +0400306 timeout_msg="Timeout for CNCF reached."
307 )
Vladimir Jigulin4ad52a82018-08-12 05:51:30 +0400308
Victor Ryzhenkin57c43202018-12-28 01:48:39 +0400309 def determine_conformance_node(self, target):
310 masters_fqdn = self._salt.get_pillar(
311 tgt='I@kubernetes:master', pillar='linux:network:fqdn')
312 node_names = [v for pillar in masters_fqdn for
313 k, v in pillar.items()]
314 return [node_name for node_name
315 in node_names
316 if node_name.startswith(target)][0]
317
Victor Ryzhenkine784bbf2018-12-21 03:58:00 +0400318 def start_conformance_inside_pod(self, cnf_type='k8s', timeout=60 * 60):
319 """
320 Create conformance pod and wait for results
321 :param cnf_type: k8s or virtlet. choose what conformance you want
322 :param timeout:
323 :return:
324 """
325 if cnf_type == 'k8s':
326 pod_mark = 'conformance'
327 elif cnf_type == 'virtlet':
328 pod_mark = 'virtlet-conformance'
329 else:
330 LOG.error("Unknown conformance type or it even not set")
331 raise RuntimeError("Unknown conformance type")
332 conformance_cmd = "kubectl apply -f /srv/kubernetes/{}.yml" \
333 "".format(pod_mark)
334 self.controller_check_call(conformance_cmd, timeout=900)
335
336 cnf_pod = self.api.pods.get(pod_mark, pod_mark)
337 cnf_pod.wait_running()
338
339 pod = cnf_pod.read()
340 target = "{}.".format(pod.spec.node_name)
Victor Ryzhenkin57c43202018-12-28 01:48:39 +0400341
342 self.conformance_node = self.determine_conformance_node(target)
Victor Ryzhenkine784bbf2018-12-21 03:58:00 +0400343
344 def cnf_status():
345 pod = cnf_pod.read()
346 status = pod.status.phase
347 LOG.info("Conformance status: {}".format(status))
348 return status
349
350 LOG.info("Waiting for Conformance to complete")
351 helpers.wait(
Aleksei Kasatkin84cc1e22019-01-18 16:55:51 +0100352 lambda: cnf_status() in ('Succeeded', 'Failed'),
Victor Ryzhenkine784bbf2018-12-21 03:58:00 +0400353 interval=120, timeout=timeout,
354 timeout_msg="Timeout for Conformance reached."
355 )
356
357 pod = cnf_pod.read()
358 status = pod.status.phase
Aleksei Kasatkin75a08032019-01-21 11:51:35 +0100359 if status == 'Failed':
Victor Ryzhenkine784bbf2018-12-21 03:58:00 +0400360 describe = "kubectl describe po {0} -n {0}".format(pod_mark)
361 LOG.info(self.controller_check_call(describe, timeout=30))
362 raise RuntimeError("Conformance failed")
363
Tatyana Leontovich619c3362019-01-04 02:17:26 +0200364 def get_node_name(self, tgt):
365 res = [node_name for node_name in
366 self.__underlay.node_names() if tgt in node_name]
367 assert len(res) > 0, 'Can not find node name by tgt {}'.format(tgt)
368 return res[0]
369
Victor Ryzhenkine784bbf2018-12-21 03:58:00 +0400370 def move_file_to_root_folder(self, filepath):
Tatyana Leontovich619c3362019-01-04 02:17:26 +0200371 cmd = "mv {0} /root/".format(filepath)
Victor Ryzhenkine784bbf2018-12-21 03:58:00 +0400372 if self.conformance_node:
Tatyana Leontovich619c3362019-01-04 02:17:26 +0200373 short_name = self.conformance_node.split('.')[0]
Victor Ryzhenkin95046882018-12-29 19:18:40 +0400374 LOG.info("Managing results on {}".format(self.conformance_node))
Tatyana Leontovich619c3362019-01-04 02:17:26 +0200375 step = {'cmd': cmd, 'node_name': self.get_node_name(
376 tgt=short_name)}
377 LOG.info('Move {0} to /root/ on {1}'.format(
378 filepath, self.conformance_node))
379 self.execute_command(step, 'Move files')
Victor Ryzhenkine784bbf2018-12-21 03:58:00 +0400380 else:
381 LOG.info("Node is not properly set")
382
Vladimir Jigulin4ad52a82018-08-12 05:51:30 +0400383 def extract_file_to_node(self, system='docker',
384 container='virtlet',
385 file_path='report.xml',
386 out_dir='.',
387 **kwargs):
Vladimir Jigulinee1faa52018-06-25 13:00:51 +0400388 """
Vladimir Jigulin4ad52a82018-08-12 05:51:30 +0400389 Download file from docker or k8s container to node
Vladimir Jigulinee1faa52018-06-25 13:00:51 +0400390
Vladimir Jigulin4ad52a82018-08-12 05:51:30 +0400391 :param system: docker or k8s
392 :param container: Full name of part of name
393 :param file_path: File path in container
394 :param kwargs: Used to control pod and namespace
395 :param out_dir: Output directory
396 :return:
Vladimir Jigulinee1faa52018-06-25 13:00:51 +0400397 """
Vladimir Jigulin0c8dd5a2018-08-28 05:08:35 +0400398 with self.__underlay.remote(node_name=self.controller_name) as remote:
Aleksei Kasatkin75a08032019-01-21 11:51:35 +0100399 if system == 'docker':
Vladimir Jigulin4ad52a82018-08-12 05:51:30 +0400400 cmd = ("docker ps --all | grep \"{0}\" |"
401 " awk '{{print $1}}'".format(container))
402 result = remote.check_call(cmd, raise_on_err=False)
403 if result['stdout']:
404 container_id = result['stdout'][0].strip()
405 else:
406 LOG.info('No container found, skipping extraction...')
407 return
408 cmd = "docker start {}".format(container_id)
409 remote.check_call(cmd, raise_on_err=False)
410 cmd = "docker cp \"{0}:/{1}\" \"{2}\"".format(
411 container_id, file_path, out_dir)
412 remote.check_call(cmd, raise_on_err=False)
413 else:
414 # system is k8s
415 pod_name = kwargs.get('pod_name')
416 pod_namespace = kwargs.get('pod_namespace')
417 cmd = 'kubectl cp \"{0}/{1}:/{2}\" \"{3}\"'.format(
418 pod_namespace, pod_name, file_path, out_dir)
419 remote.check_call(cmd, raise_on_err=False)
Vladimir Jigulina6b018b2018-07-18 15:19:01 +0400420
Vladimir Jigulin4ad52a82018-08-12 05:51:30 +0400421 def download_k8s_logs(self, files):
422 """
423 Download JUnit report and conformance logs from cluster
424 :param files:
425 :return:
426 """
Victor Ryzhenkine784bbf2018-12-21 03:58:00 +0400427 if self.conformance_node:
428 node = self.conformance_node
429 else:
430 node = self.controller_name
431 LOG.info("Trying to get logs at {}".format(node))
Vladimir Jigulin4ad52a82018-08-12 05:51:30 +0400432 master_host = self.__config.salt.salt_master_host
433 with self.__underlay.remote(host=master_host) as r:
434 for log_file in files:
Tatyana Leontovich619c3362019-01-04 02:17:26 +0200435 cmd = "scp -r \"{0}:/root/{1}\" /root/".format(
Victor Ryzhenkine784bbf2018-12-21 03:58:00 +0400436 node, log_file)
Tatyana Leontovich48a33c12019-01-03 02:19:25 +0200437 r.check_call(cmd, raise_on_err=True)
Vladimir Jigulin4ad52a82018-08-12 05:51:30 +0400438 LOG.info("Downloading the artifact {0}".format(log_file))
439 r.download(destination=log_file, target=os.getcwd())
Dennis Dmitriev445b4322018-10-08 14:32:07 +0300440 self.store_server_version(os.path.join(os.getcwd(), 'env_k8s_version'))
Vladimir Jigulina6b018b2018-07-18 15:19:01 +0400441
Vladimir Jigulin4ad52a82018-08-12 05:51:30 +0400442 def combine_xunit(self, path, output):
443 """
444 Function to combine multiple xmls with test results to
445 one.
Vladimir Jigulin34dfa942018-07-23 21:05:48 +0400446
Vladimir Jigulin4ad52a82018-08-12 05:51:30 +0400447 :param path: Path where xmls to combine located
448 :param output: Path to xml file where output will stored
449 :return:
450 """
Victor Ryzhenkine784bbf2018-12-21 03:58:00 +0400451 if self.conformance_node:
452 node = self.conformance_node
453 else:
454 node = self.controller_name
455 LOG.info("Trying to combine xunit at {}".format(node))
Victor Ryzhenkin95046882018-12-29 19:18:40 +0400456 cmd = ("apt-get install python-setuptools -y; "
457 "pip install "
458 "https://github.com/mogaika/xunitmerge/archive/master.zip")
459 LOG.debug('Installing xunitmerge')
460 self._salt.cmd_run(tgt=node, cmd=cmd)
461 LOG.debug('Merging xunit')
462 cmd = ("cd {0}; arg=''; "
463 "for i in $(ls | grep xml); "
464 "do arg=\"$arg $i\"; done && "
465 "xunitmerge $arg {1} || true".format(path, output))
466 self._salt.cmd_run(tgt=node, cmd=cmd)
Vladimir Jigulin34dfa942018-07-23 21:05:48 +0400467
Vladimir Jigulin4ad52a82018-08-12 05:51:30 +0400468 def manage_cncf_archive(self):
469 """
Vladimir Jigulin0c8dd5a2018-08-28 05:08:35 +0400470 Function to untar archive, move files that we are needs to the
471 home folder, prepare it to downloading.
472 Will generate files on controller node:
473 e2e.log, junit_01.xml, cncf_results.tar.gz, version.txt
Vladimir Jigulin4ad52a82018-08-12 05:51:30 +0400474 :return:
475 """
476
Vladimir Jigulin0c8dd5a2018-08-28 05:08:35 +0400477 cmd =\
478 "rm -rf cncf_results.tar.gz result && " \
479 "mkdir result && " \
480 "mv *_sonobuoy_*.tar.gz cncf_results.tar.gz && " \
481 "tar -C result -xzf cncf_results.tar.gz && " \
482 "mv result/plugins/e2e/results/e2e.log . ; " \
483 "mv result/plugins/e2e/results/junit_01.xml . ; " \
484 "kubectl version > version.txt"
Vladimir Jigulin4ad52a82018-08-12 05:51:30 +0400485
Vladimir Jigulin0c8dd5a2018-08-28 05:08:35 +0400486 self.controller_check_call(cmd, raise_on_err=False)
Vladimir Jigulin4ad52a82018-08-12 05:51:30 +0400487
488 @retry(300, exception=DevopsCalledProcessError)
489 def nslookup(self, host, src):
490 """ Run nslookup on controller and return result """
491 return self.controller_check_call("nslookup {0} {1}".format(host, src))
492
493 @retry(300, exception=DevopsCalledProcessError)
Vladimir Jigulin57ecae92018-09-10 22:51:15 +0400494 def curl(self, url, *args):
Vladimir Jigulin4ad52a82018-08-12 05:51:30 +0400495 """
496 Run curl on controller and return stdout
497
498 :param url: url to curl
Vladimir Jigulin57ecae92018-09-10 22:51:15 +0400499 :return: list of strings (with /n at end of every line)
Vladimir Jigulin4ad52a82018-08-12 05:51:30 +0400500 """
Vladimir Jigulin57ecae92018-09-10 22:51:15 +0400501 args = list(args)
502 args.append(url)
503 cmd = "curl -s -S {}".format(
504 " ".join(["'{}'".format(a.replace("'", "\\'")) for a in args]))
505 result = self.controller_check_call(cmd)
506 LOG.debug("{0}\nresult:\n{1}".format(cmd, result['stdout']))
Vladimir Jigulin4ad52a82018-08-12 05:51:30 +0400507 return result['stdout']
Vladimir Jigulin34dfa942018-07-23 21:05:48 +0400508
Dennis Dmitriev445b4322018-10-08 14:32:07 +0300509 def store_server_version(self, env_file_path):
510 """Store Kubernetes server version in bash source file"""
511
512 def digits(string):
513 return ''.join(n for n in string if n.isdigit())
514
515 ver = self.api.api_version.get_code()
516 LOG.debug("Got Kubernetes server version:\n{0}".format(ver))
517
518 env_version = ("export KUBE_SERVER_VERSION={0}.{1}\n"
519 "export KUBE_SERVER_GIT_VERSION={2}\n"
520 .format(digits(ver.major),
521 digits(ver.minor),
522 ver.git_version))
523
524 LOG.info("Kubernetes server version is stored to {0}:\n{1}"
525 .format(env_file_path, env_version))
526 with open(env_file_path, 'w') as kver:
527 kver.write(env_version)
528
Vladimir Jigulina6b018b2018-07-18 15:19:01 +0400529
Vladimir Jigulin4ad52a82018-08-12 05:51:30 +0400530class K8SKubectlCli(object):
531 """ Contain kubectl cli commands and api wrappers"""
532 def __init__(self, manager):
533 self._manager = manager
Vladimir Jigulina6b018b2018-07-18 15:19:01 +0400534
Vladimir Jigulin4ad52a82018-08-12 05:51:30 +0400535 def cli_run(self, namespace, name, image, port, replicas=1):
536 cmd = "kubectl -n {0} run {1} --image={2} --port={3} --replicas={4}".\
537 format(namespace, name, image, port, replicas)
538 return self._manager.controller_check_call(cmd)
Vladimir Jigulina6b018b2018-07-18 15:19:01 +0400539
Vladimir Jigulin4ad52a82018-08-12 05:51:30 +0400540 def run(self, namespace, name, image, port, replicas=1):
541 self.cli_run(namespace, name, image, port, replicas)
542 return self._manager.api.deployments.get(
543 namespace=namespace, name=name)
Vladimir Jigulina6b018b2018-07-18 15:19:01 +0400544
Vladimir Jigulin4ad52a82018-08-12 05:51:30 +0400545 def cli_expose(self, namespace, resource_type, resource_name,
546 service_name=None, port='', service_type='ClusterIP'):
547 cmd = "kubectl -n {0} expose {1} {2} --port={3} --type={4}".format(
548 namespace, resource_type, resource_name, port, service_type)
549 if service_name is not None:
550 cmd += " --name={}".format(service_name)
551 return self._manager.controller_check_call(cmd)
Vladimir Jigulina6b018b2018-07-18 15:19:01 +0400552
Vladimir Jigulin4ad52a82018-08-12 05:51:30 +0400553 def expose(self, resource, service_name=None,
554 port='', service_type='ClusterIP'):
555 self.cli_expose(resource.namespace, resource.resource_type,
556 resource.name, service_name=service_name,
557 port=port, service_type=service_type)
558 return self._manager.api.services.get(
559 namespace=resource.namespace, name=service_name or resource.name)
560
561 def cli_exec(self, namespace, pod_name, cmd, container=''):
562 kubectl_cmd = "kubectl -n {0} exec --container={1} {2} -- {3}".format(
563 namespace, container, pod_name, cmd)
564 return self._manager.controller_check_call(kubectl_cmd)
565
566 # def exec(...), except exec is statement in python
567 def execute(self, pod, cmd, container=''):
568 return self.cli_exec(pod.namespace, pod.name, cmd, container=container)
569
570 def cli_annotate(self, namespace, resource_type, resource_name,
571 annotations, overwrite=False):
572 cmd = "kubectl -n {0} annotate {1} {2} {3}".format(
573 namespace, resource_type, resource_name, annotations)
574 if overwrite:
575 cmd += " --overwrite"
576 return self._manager.controller_check_call(cmd)
577
578 def annotate(self, resource, annotations, overwrite=False):
579 return self.cli_annotate(resource.namespace, resource.resource_type,
580 resource.name, annotations,
581 overwrite=overwrite)
582
583
584class K8SVirtlet(object):
585 """ Contain virtlet-related methods"""
586 def __init__(self, manager, namespace='kube-system'):
587 self._manager = manager
588 self._namespace = namespace
589
590 def get_virtlet_node_pod(self, node_name):
591 for pod in self._manager.api.pods.list(
592 namespace=self._namespace, name_prefix='virtlet-'):
593 if pod.read().spec.node_name == node_name:
594 return pod
595 return None
596
597 def get_pod_dom_uuid(self, pod):
598 uuid_name_map = self.virtlet_execute(
599 pod.read().spec.node_name, 'virsh list --uuid --name')['stdout']
Vladimir Jigulin4ad52a82018-08-12 05:51:30 +0400600 for line in uuid_name_map:
601 if line.rstrip().endswith("-{}".format(pod.name)):
602 return line.split(" ")[0]
603 raise Exception("Cannot detect uuid for pod {}".format(pod.name))
604
605 def virsh_domstats(self, pod):
606 """ get dict of vm stats """
607 uuid = self.get_pod_dom_uuid(pod)
608 result = self.virtlet_execute(
609 pod.read().spec.node_name, 'virsh domstats {}'.format(uuid))
610 stats = dict()
611 for line in result['stdout']:
612 if '=' in line:
613 vals = line.strip().split('=')
614 stats[vals[0]] = vals[1]
615 return stats
616
617 def virtlet_execute(self, node_name, cmd, container='libvirt'):
618 """ execute command inside virtlet container """
619 pod = self.get_virtlet_node_pod(node_name)
620 return self._manager.kubectl.execute(pod, cmd, container)
621
622
623class K8SSampleDeployment(object):
624 """ Wrapper for deployment run=>expose=>check frequent combination """
625 def __init__(self, manager, name,
626 namespace=None,
627 image='gcr.io/google-samples/node-hello:1.0',
628 port=8080,
629 replicas=2):
630 namespace = namespace or manager.api.default_namespace
631
632 self._manager = manager
633 self._port = port
634 self._deployment = \
635 manager.kubectl.run(namespace, name,
636 image=image, port=port, replicas=replicas)
637 self._index = 1 # used to generate svc name
638 self._svc = None # hold last created svc
639
640 def wait_ready(self, timeout=300, interval=5):
641 self._deployment.wait_ready(timeout=timeout, interval=interval)
642 return self
643
644 def svc(self):
645 """ Return the last exposed service"""
646 return self._svc
647
648 def expose(self, service_type='ClusterIP'):
649 service_name = "{0}-s{1}".format(self._deployment.name, self._index)
Vladimir Jigulin90689152018-09-26 15:38:19 +0400650 self._index += 1
Vladimir Jigulin4ad52a82018-08-12 05:51:30 +0400651 self._svc = self._manager.kubectl.expose(
652 self._deployment, port=self._port,
653 service_name=service_name, service_type=service_type)
654 return self._svc
655
656 def curl(self, svc=None, external=False):
657 if svc is None:
658 svc = self.svc()
659 url = "http://{0}:{1}".format(svc.get_ip(external), self._port)
Vladimir Jigulina6b018b2018-07-18 15:19:01 +0400660 if external:
661 return requests.get(url).text
662 else:
Vladimir Jigulin4ad52a82018-08-12 05:51:30 +0400663 return self._manager.curl(url)
Vladimir Jigulina6b018b2018-07-18 15:19:01 +0400664
Vladimir Jigulin4ad52a82018-08-12 05:51:30 +0400665 def is_service_available(self, svc=None, external=False):
666 return "Hello Kubernetes!" in self.curl(svc, external=external)
Vladimir Jigulin90689152018-09-26 15:38:19 +0400667
668 def delete(self):
669 for svc in self._manager.api.services.list_all(
670 name_prefix="{}-s".format(self._deployment.name)):
671 svc.delete()
672 self._deployment.delete()