blob: d1a9a8712d6b0cd4689088ed92c8cbb64e655b35 [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
Victor Ryzhenkin66d39372017-09-28 19:25:48 +0400108 @property
Vladimir Jigulin4ad52a82018-08-12 05:51:30 +0400109 def controller_name(self):
110 """ Return node name of controller node that used for all actions """
Oleksii Butenko4c05ae02019-01-31 10:51:33 +0000111 names = [node['node_name'] for node in self.get_controllers()]
Vladimir Jigulin4ad52a82018-08-12 05:51:30 +0400112 # we want to return same controller name every time
113 names.sort()
114 return names[0]
Victor Ryzhenkin66d39372017-09-28 19:25:48 +0400115
Dennis Dmitriev66650fc2018-11-02 11:04:37 +0200116 @property
117 def controller_minion_id(self):
118 """ Return node name of controller node that used for all actions """
119 minion_ids = [minion_id['minion_id'] for minion_id in
120 self.get_controllers()]
121 # we want to return same controller name every time
122 minion_ids.sort()
123 return minion_ids[0]
124
125 @property
126 def is_metallb_enabled(self):
127 ctl_tgt = self.controller_minion_id
128 LOG.debug("Controller target: {}".format(ctl_tgt))
129
130 result = self._salt.get_pillar(
131 tgt=ctl_tgt,
132 pillar='kubernetes:common:addons:metallb:enabled')
133 metallb = result[0].get(ctl_tgt, False)
134 LOG.info("{} kubernetes:common:addons:metallb:enabled: {}"
135 .format(ctl_tgt, bool(metallb)))
136 return metallb
137
138 @property
139 def is_ingress_nginx_enabled(self):
140 ctl_tgt = self.controller_minion_id
141 LOG.debug("Controller target: {}".format(ctl_tgt))
142
143 result = self._salt.get_pillar(
144 tgt=ctl_tgt,
145 pillar='kubernetes:common:addons:ingress-nginx:enabled')
146 ingress_nginx = result[0].get(ctl_tgt, False)
147 LOG.info("{} kubernetes:common:addons:ingress-nginx:enabled: {}"
148 .format(ctl_tgt, bool(ingress_nginx)))
149 return ingress_nginx
150
Vladimir Jigulin4ad52a82018-08-12 05:51:30 +0400151 def controller_check_call(self, cmd, **kwargs):
152 """ Run command on controller and return result """
153 LOG.info("running cmd on k8s controller: {}".format(cmd))
Vladimir Jigulinee1faa52018-06-25 13:00:51 +0400154 return self.__underlay.check_call(
Vladimir Jigulin4ad52a82018-08-12 05:51:30 +0400155 cmd=cmd, node_name=self.controller_name, **kwargs)
Artem Panchenko501e67e2017-06-14 14:59:18 +0300156
Vladimir Jigulin4ad52a82018-08-12 05:51:30 +0400157 def get_keepalived_vip(self):
Vladimir Jigulin62bcf462018-05-28 18:17:01 +0400158 """
Vladimir Jigulin4ad52a82018-08-12 05:51:30 +0400159 Return k8s VIP IP address
Vladimir Jigulin62bcf462018-05-28 18:17:01 +0400160
Vladimir Jigulin4ad52a82018-08-12 05:51:30 +0400161 :return: str, IP address
Vladimir Jigulin62bcf462018-05-28 18:17:01 +0400162 """
Vladimir Jigulin4ad52a82018-08-12 05:51:30 +0400163 ctl_vip_pillar = self._salt.get_pillar(
Vladimir Jigulinc69f4ba2019-01-14 15:27:44 +0400164 tgt="I@kubernetes:control",
Vladimir Jigulin4ad52a82018-08-12 05:51:30 +0400165 pillar="_param:cluster_vip_address")[0]
166 return ctl_vip_pillar.values()[0]
Vladimir Jigulin62bcf462018-05-28 18:17:01 +0400167
Vladimir Jigulin4ad52a82018-08-12 05:51:30 +0400168 def run_sample_deployment(self, name, **kwargs):
169 return K8SSampleDeployment(self, name, **kwargs)
Victor Ryzhenkin3ffa2b42017-10-05 16:38:44 +0400170
Vladimir Jigulin4ad52a82018-08-12 05:51:30 +0400171 def get_pod_ips_from_container(self, pod_name, exclude_local=True,
172 namespace='default'):
173 """ Get ips from container using 'ip a'
174 Required for cni-genie multi-cni cases
175
176 :return: list of IP adresses
Victor Ryzhenkin3ffa2b42017-10-05 16:38:44 +0400177 """
Vladimir Jigulin4ad52a82018-08-12 05:51:30 +0400178 cmd = "ip a|grep \"inet \"|awk '{{print $2}}'"
179 result = self.kubectl.cli_exec(namespace, pod_name, cmd)['stdout']
180 ips = [line.strip().split('/')[0] for line in result]
181 if exclude_local:
182 ips = [ip for ip in ips if not ip.startswith("127.")]
183 return ips
Victor Ryzhenkin3ffa2b42017-10-05 16:38:44 +0400184
Vladimir Jigulin4ad52a82018-08-12 05:51:30 +0400185 def update_k8s_version(self, tag):
Vladimir Jigulin62bcf462018-05-28 18:17:01 +0400186 """
187 Update k8s images tag version in cluster meta and apply required
188 for update states
189
190 :param tag: New version tag of k8s images
191 :return:
192 """
193 master_host = self.__config.salt.salt_master_host
194
195 def update_image_tag_meta(config, image_name):
196 image_old = config.get(image_name)
197 image_base = image_old.split(':')[0]
198 image_new = "{}:{}".format(image_base, tag)
199 LOG.info("Changing k8s '{0}' image cluster meta to '{1}'".format(
200 image_name, image_new))
201
202 with self.__underlay.remote(host=master_host) as r:
203 cmd = "salt-call reclass.cluster_meta_set" \
204 " name={0} value={1}".format(image_name, image_new)
205 r.check_call(cmd)
206 return image_new
207
208 cfg = self.__config
209
210 update_image_tag_meta(cfg.k8s_deploy, "kubernetes_hyperkube_image")
211 update_image_tag_meta(cfg.k8s_deploy, "kubernetes_pause_image")
212 cfg.k8s.k8s_conformance_image = update_image_tag_meta(
213 cfg.k8s, "k8s_conformance_image")
214
215 steps_path = cfg.k8s_deploy.k8s_update_steps_path
216 update_commands = self.__underlay.read_template(steps_path)
217 self.execute_commands(
218 update_commands, label="Updating kubernetes to '{}'".format(tag))
Vladimir Jigulinee1faa52018-06-25 13:00:51 +0400219
Vladimir Jigulin4ad52a82018-08-12 05:51:30 +0400220 def run_conformance(self, timeout=60*60, log_out='k8s_conformance.log',
221 raise_on_err=True, node_name=None,
222 api_server='http://127.0.0.1:8080'):
223 if node_name is None:
224 node_name = self.controller_name
225 cmd = "set -o pipefail; docker run --net=host " \
226 "-e API_SERVER='{api}' {image} | tee '{log}'".format(
227 api=api_server, log=log_out,
228 image=self.__config.k8s.k8s_conformance_image)
229 return self.__underlay.check_call(
230 cmd=cmd, node_name=node_name, timeout=timeout,
Dennis Dmitrieveb50ce12018-09-27 13:34:32 +0300231 raise_on_err=raise_on_err, verbose=True)
Vladimir Jigulin4ad52a82018-08-12 05:51:30 +0400232
233 def run_virtlet_conformance(self, timeout=60 * 120,
Dennis Dmitriev34fd3002018-11-15 18:25:16 +0200234 log_file='virtlet_conformance.log',
235 report_name="report.xml"):
Vladimir Jigulin4ad52a82018-08-12 05:51:30 +0400236 if self.__config.k8s.run_extended_virtlet_conformance:
237 ci_image = "cloud-images.ubuntu.com/xenial/current/" \
238 "xenial-server-cloudimg-amd64-disk1.img"
239 cmd = ("set -o pipefail; "
240 "docker run --net=host {0} /virtlet-e2e-tests "
Dennis Dmitriev34fd3002018-11-15 18:25:16 +0200241 "-include-cloud-init-tests -junitOutput {3} "
Vladimir Jigulin4ad52a82018-08-12 05:51:30 +0400242 "-image {2} -sshuser ubuntu -memoryLimit 1024 "
243 "-alsologtostderr -cluster-url http://127.0.0.1:8080 "
244 "-ginkgo.focus '\[Conformance\]' "
245 "| tee {1}".format(
246 self.__config.k8s_deploy.kubernetes_virtlet_image,
Dennis Dmitriev34fd3002018-11-15 18:25:16 +0200247 log_file, ci_image, report_name))
Vladimir Jigulin4ad52a82018-08-12 05:51:30 +0400248 else:
249 cmd = ("set -o pipefail; "
250 "docker run --net=host {0} /virtlet-e2e-tests "
Dennis Dmitriev34fd3002018-11-15 18:25:16 +0200251 "-junitOutput {2} "
Vladimir Jigulin4ad52a82018-08-12 05:51:30 +0400252 "-alsologtostderr -cluster-url http://127.0.0.1:8080 "
253 "-ginkgo.focus '\[Conformance\]' "
254 "| tee {1}".format(
255 self.__config.k8s_deploy.kubernetes_virtlet_image,
Dennis Dmitriev34fd3002018-11-15 18:25:16 +0200256 log_file, report_name))
Vladimir Jigulin4ad52a82018-08-12 05:51:30 +0400257 LOG.info("Executing: {}".format(cmd))
258 with self.__underlay.remote(
259 node_name=self.controller_name) as remote:
260 result = remote.check_call(cmd, timeout=timeout)
261 stderr = result['stderr']
262 stdout = result['stdout']
263 LOG.info("Test results stdout: {}".format(stdout))
264 LOG.info("Test results stderr: {}".format(stderr))
265 return result
266
Vladimir Jigulin51611672018-11-23 03:10:22 +0400267 def start_k8s_cncf_verification(self, timeout=60 * 180):
Vladimir Jigulin0c8dd5a2018-08-28 05:08:35 +0400268 """
269 Build sonobuoy using golang docker image and install it in system
270 Then generate sonobuoy verification manifest using gen command
271 and wait for it to end using status annotation
272 TODO (vjigulin): All sonobuoy methods can be improved if we define
273 correct KUBECONFIG env variable
274 """
275 cncf_cmd =\
276 'docker run --network host --name sonobuoy golang:1.11 bash -c ' \
277 '"go get -d -v github.com/heptio/sonobuoy && ' \
278 'go install -v github.com/heptio/sonobuoy" && ' \
279 'docker cp sonobuoy:/go/bin/sonobuoy /usr/local/bin/ && ' \
280 'docker rm sonobuoy && ' \
281 'sonobuoy gen | kubectl apply -f -'
Vladimir Jigulin4ad52a82018-08-12 05:51:30 +0400282
Vladimir Jigulin0c8dd5a2018-08-28 05:08:35 +0400283 self.controller_check_call(cncf_cmd, timeout=900)
284
285 sonobuoy_pod = self.api.pods.get('sonobuoy', 'heptio-sonobuoy')
286 sonobuoy_pod.wait_running()
287
288 def sonobuoy_status():
289 annotations = sonobuoy_pod.read().metadata.annotations
290 json_status = annotations['sonobuoy.hept.io/status']
291 status = yaml.safe_load(json_status)['status']
292 if status != 'running':
293 LOG.info("CNCF status: {}".format(json_status))
294 return yaml.safe_load(json_status)['status']
295
296 LOG.info("Waiting for CNCF to complete")
297 helpers.wait(
298 lambda: sonobuoy_status() == 'complete',
Vladimir Jigulin51611672018-11-23 03:10:22 +0400299 interval=120, timeout=timeout,
Vladimir Jigulin0c8dd5a2018-08-28 05:08:35 +0400300 timeout_msg="Timeout for CNCF reached."
301 )
Vladimir Jigulin4ad52a82018-08-12 05:51:30 +0400302
Victor Ryzhenkin57c43202018-12-28 01:48:39 +0400303 def determine_conformance_node(self, target):
304 masters_fqdn = self._salt.get_pillar(
305 tgt='I@kubernetes:master', pillar='linux:network:fqdn')
306 node_names = [v for pillar in masters_fqdn for
307 k, v in pillar.items()]
308 return [node_name for node_name
309 in node_names
310 if node_name.startswith(target)][0]
311
Victor Ryzhenkine784bbf2018-12-21 03:58:00 +0400312 def start_conformance_inside_pod(self, cnf_type='k8s', timeout=60 * 60):
313 """
314 Create conformance pod and wait for results
315 :param cnf_type: k8s or virtlet. choose what conformance you want
316 :param timeout:
317 :return:
318 """
319 if cnf_type == 'k8s':
320 pod_mark = 'conformance'
321 elif cnf_type == 'virtlet':
322 pod_mark = 'virtlet-conformance'
323 else:
324 LOG.error("Unknown conformance type or it even not set")
325 raise RuntimeError("Unknown conformance type")
326 conformance_cmd = "kubectl apply -f /srv/kubernetes/{}.yml" \
327 "".format(pod_mark)
328 self.controller_check_call(conformance_cmd, timeout=900)
329
330 cnf_pod = self.api.pods.get(pod_mark, pod_mark)
331 cnf_pod.wait_running()
332
333 pod = cnf_pod.read()
334 target = "{}.".format(pod.spec.node_name)
Victor Ryzhenkin57c43202018-12-28 01:48:39 +0400335
336 self.conformance_node = self.determine_conformance_node(target)
Victor Ryzhenkine784bbf2018-12-21 03:58:00 +0400337
338 def cnf_status():
339 pod = cnf_pod.read()
340 status = pod.status.phase
341 LOG.info("Conformance status: {}".format(status))
342 return status
343
344 LOG.info("Waiting for Conformance to complete")
345 helpers.wait(
Aleksei Kasatkin84cc1e22019-01-18 16:55:51 +0100346 lambda: cnf_status() in ('Succeeded', 'Failed'),
Victor Ryzhenkine784bbf2018-12-21 03:58:00 +0400347 interval=120, timeout=timeout,
348 timeout_msg="Timeout for Conformance reached."
349 )
350
351 pod = cnf_pod.read()
352 status = pod.status.phase
Aleksei Kasatkin75a08032019-01-21 11:51:35 +0100353 if status == 'Failed':
Victor Ryzhenkine784bbf2018-12-21 03:58:00 +0400354 describe = "kubectl describe po {0} -n {0}".format(pod_mark)
355 LOG.info(self.controller_check_call(describe, timeout=30))
356 raise RuntimeError("Conformance failed")
357
Tatyana Leontovich619c3362019-01-04 02:17:26 +0200358 def get_node_name(self, tgt):
359 res = [node_name for node_name in
360 self.__underlay.node_names() if tgt in node_name]
361 assert len(res) > 0, 'Can not find node name by tgt {}'.format(tgt)
362 return res[0]
363
Victor Ryzhenkine784bbf2018-12-21 03:58:00 +0400364 def move_file_to_root_folder(self, filepath):
Tatyana Leontovich619c3362019-01-04 02:17:26 +0200365 cmd = "mv {0} /root/".format(filepath)
Victor Ryzhenkine784bbf2018-12-21 03:58:00 +0400366 if self.conformance_node:
Tatyana Leontovich619c3362019-01-04 02:17:26 +0200367 short_name = self.conformance_node.split('.')[0]
Victor Ryzhenkin95046882018-12-29 19:18:40 +0400368 LOG.info("Managing results on {}".format(self.conformance_node))
Tatyana Leontovich619c3362019-01-04 02:17:26 +0200369 step = {'cmd': cmd, 'node_name': self.get_node_name(
370 tgt=short_name)}
371 LOG.info('Move {0} to /root/ on {1}'.format(
372 filepath, self.conformance_node))
373 self.execute_command(step, 'Move files')
Victor Ryzhenkine784bbf2018-12-21 03:58:00 +0400374 else:
375 LOG.info("Node is not properly set")
376
Vladimir Jigulin4ad52a82018-08-12 05:51:30 +0400377 def extract_file_to_node(self, system='docker',
378 container='virtlet',
379 file_path='report.xml',
380 out_dir='.',
381 **kwargs):
Vladimir Jigulinee1faa52018-06-25 13:00:51 +0400382 """
Vladimir Jigulin4ad52a82018-08-12 05:51:30 +0400383 Download file from docker or k8s container to node
Vladimir Jigulinee1faa52018-06-25 13:00:51 +0400384
Vladimir Jigulin4ad52a82018-08-12 05:51:30 +0400385 :param system: docker or k8s
386 :param container: Full name of part of name
387 :param file_path: File path in container
388 :param kwargs: Used to control pod and namespace
389 :param out_dir: Output directory
390 :return:
Vladimir Jigulinee1faa52018-06-25 13:00:51 +0400391 """
Vladimir Jigulin0c8dd5a2018-08-28 05:08:35 +0400392 with self.__underlay.remote(node_name=self.controller_name) as remote:
Aleksei Kasatkin75a08032019-01-21 11:51:35 +0100393 if system == 'docker':
Vladimir Jigulin4ad52a82018-08-12 05:51:30 +0400394 cmd = ("docker ps --all | grep \"{0}\" |"
395 " awk '{{print $1}}'".format(container))
396 result = remote.check_call(cmd, raise_on_err=False)
397 if result['stdout']:
398 container_id = result['stdout'][0].strip()
399 else:
400 LOG.info('No container found, skipping extraction...')
401 return
402 cmd = "docker start {}".format(container_id)
403 remote.check_call(cmd, raise_on_err=False)
404 cmd = "docker cp \"{0}:/{1}\" \"{2}\"".format(
405 container_id, file_path, out_dir)
406 remote.check_call(cmd, raise_on_err=False)
407 else:
408 # system is k8s
409 pod_name = kwargs.get('pod_name')
410 pod_namespace = kwargs.get('pod_namespace')
411 cmd = 'kubectl cp \"{0}/{1}:/{2}\" \"{3}\"'.format(
412 pod_namespace, pod_name, file_path, out_dir)
413 remote.check_call(cmd, raise_on_err=False)
Vladimir Jigulina6b018b2018-07-18 15:19:01 +0400414
Vladimir Jigulin4ad52a82018-08-12 05:51:30 +0400415 def download_k8s_logs(self, files):
416 """
417 Download JUnit report and conformance logs from cluster
418 :param files:
419 :return:
420 """
Victor Ryzhenkine784bbf2018-12-21 03:58:00 +0400421 if self.conformance_node:
422 node = self.conformance_node
423 else:
424 node = self.controller_name
425 LOG.info("Trying to get logs at {}".format(node))
Vladimir Jigulin4ad52a82018-08-12 05:51:30 +0400426 master_host = self.__config.salt.salt_master_host
427 with self.__underlay.remote(host=master_host) as r:
428 for log_file in files:
Tatyana Leontovich619c3362019-01-04 02:17:26 +0200429 cmd = "scp -r \"{0}:/root/{1}\" /root/".format(
Victor Ryzhenkine784bbf2018-12-21 03:58:00 +0400430 node, log_file)
Tatyana Leontovich48a33c12019-01-03 02:19:25 +0200431 r.check_call(cmd, raise_on_err=True)
Vladimir Jigulin4ad52a82018-08-12 05:51:30 +0400432 LOG.info("Downloading the artifact {0}".format(log_file))
433 r.download(destination=log_file, target=os.getcwd())
Dennis Dmitriev445b4322018-10-08 14:32:07 +0300434 self.store_server_version(os.path.join(os.getcwd(), 'env_k8s_version'))
Vladimir Jigulina6b018b2018-07-18 15:19:01 +0400435
Vladimir Jigulin4ad52a82018-08-12 05:51:30 +0400436 def combine_xunit(self, path, output):
437 """
438 Function to combine multiple xmls with test results to
439 one.
Vladimir Jigulin34dfa942018-07-23 21:05:48 +0400440
Vladimir Jigulin4ad52a82018-08-12 05:51:30 +0400441 :param path: Path where xmls to combine located
442 :param output: Path to xml file where output will stored
443 :return:
444 """
Victor Ryzhenkine784bbf2018-12-21 03:58:00 +0400445 if self.conformance_node:
446 node = self.conformance_node
447 else:
448 node = self.controller_name
449 LOG.info("Trying to combine xunit at {}".format(node))
Victor Ryzhenkin95046882018-12-29 19:18:40 +0400450 cmd = ("apt-get install python-setuptools -y; "
451 "pip install "
452 "https://github.com/mogaika/xunitmerge/archive/master.zip")
453 LOG.debug('Installing xunitmerge')
454 self._salt.cmd_run(tgt=node, cmd=cmd)
455 LOG.debug('Merging xunit')
456 cmd = ("cd {0}; arg=''; "
457 "for i in $(ls | grep xml); "
458 "do arg=\"$arg $i\"; done && "
459 "xunitmerge $arg {1} || true".format(path, output))
460 self._salt.cmd_run(tgt=node, cmd=cmd)
Vladimir Jigulin34dfa942018-07-23 21:05:48 +0400461
Vladimir Jigulin4ad52a82018-08-12 05:51:30 +0400462 def manage_cncf_archive(self):
463 """
Vladimir Jigulin0c8dd5a2018-08-28 05:08:35 +0400464 Function to untar archive, move files that we are needs to the
465 home folder, prepare it to downloading.
466 Will generate files on controller node:
467 e2e.log, junit_01.xml, cncf_results.tar.gz, version.txt
Vladimir Jigulin4ad52a82018-08-12 05:51:30 +0400468 :return:
469 """
470
Vladimir Jigulin0c8dd5a2018-08-28 05:08:35 +0400471 cmd =\
472 "rm -rf cncf_results.tar.gz result && " \
473 "mkdir result && " \
474 "mv *_sonobuoy_*.tar.gz cncf_results.tar.gz && " \
475 "tar -C result -xzf cncf_results.tar.gz && " \
476 "mv result/plugins/e2e/results/e2e.log . ; " \
477 "mv result/plugins/e2e/results/junit_01.xml . ; " \
478 "kubectl version > version.txt"
Vladimir Jigulin4ad52a82018-08-12 05:51:30 +0400479
Vladimir Jigulin0c8dd5a2018-08-28 05:08:35 +0400480 self.controller_check_call(cmd, raise_on_err=False)
Vladimir Jigulin4ad52a82018-08-12 05:51:30 +0400481
482 @retry(300, exception=DevopsCalledProcessError)
483 def nslookup(self, host, src):
484 """ Run nslookup on controller and return result """
485 return self.controller_check_call("nslookup {0} {1}".format(host, src))
486
487 @retry(300, exception=DevopsCalledProcessError)
Vladimir Jigulin57ecae92018-09-10 22:51:15 +0400488 def curl(self, url, *args):
Vladimir Jigulin4ad52a82018-08-12 05:51:30 +0400489 """
490 Run curl on controller and return stdout
491
492 :param url: url to curl
Vladimir Jigulin57ecae92018-09-10 22:51:15 +0400493 :return: list of strings (with /n at end of every line)
Vladimir Jigulin4ad52a82018-08-12 05:51:30 +0400494 """
Vladimir Jigulin57ecae92018-09-10 22:51:15 +0400495 args = list(args)
496 args.append(url)
497 cmd = "curl -s -S {}".format(
498 " ".join(["'{}'".format(a.replace("'", "\\'")) for a in args]))
499 result = self.controller_check_call(cmd)
500 LOG.debug("{0}\nresult:\n{1}".format(cmd, result['stdout']))
Vladimir Jigulin4ad52a82018-08-12 05:51:30 +0400501 return result['stdout']
Vladimir Jigulin34dfa942018-07-23 21:05:48 +0400502
Dennis Dmitriev445b4322018-10-08 14:32:07 +0300503 def store_server_version(self, env_file_path):
504 """Store Kubernetes server version in bash source file"""
505
506 def digits(string):
507 return ''.join(n for n in string if n.isdigit())
508
509 ver = self.api.api_version.get_code()
510 LOG.debug("Got Kubernetes server version:\n{0}".format(ver))
511
512 env_version = ("export KUBE_SERVER_VERSION={0}.{1}\n"
513 "export KUBE_SERVER_GIT_VERSION={2}\n"
514 .format(digits(ver.major),
515 digits(ver.minor),
516 ver.git_version))
517
518 LOG.info("Kubernetes server version is stored to {0}:\n{1}"
519 .format(env_file_path, env_version))
520 with open(env_file_path, 'w') as kver:
521 kver.write(env_version)
522
Vladimir Jigulina6b018b2018-07-18 15:19:01 +0400523
Vladimir Jigulin4ad52a82018-08-12 05:51:30 +0400524class K8SKubectlCli(object):
525 """ Contain kubectl cli commands and api wrappers"""
526 def __init__(self, manager):
527 self._manager = manager
Vladimir Jigulina6b018b2018-07-18 15:19:01 +0400528
Vladimir Jigulin4ad52a82018-08-12 05:51:30 +0400529 def cli_run(self, namespace, name, image, port, replicas=1):
530 cmd = "kubectl -n {0} run {1} --image={2} --port={3} --replicas={4}".\
531 format(namespace, name, image, port, replicas)
532 return self._manager.controller_check_call(cmd)
Vladimir Jigulina6b018b2018-07-18 15:19:01 +0400533
Vladimir Jigulin4ad52a82018-08-12 05:51:30 +0400534 def run(self, namespace, name, image, port, replicas=1):
535 self.cli_run(namespace, name, image, port, replicas)
536 return self._manager.api.deployments.get(
537 namespace=namespace, name=name)
Vladimir Jigulina6b018b2018-07-18 15:19:01 +0400538
Vladimir Jigulin4ad52a82018-08-12 05:51:30 +0400539 def cli_expose(self, namespace, resource_type, resource_name,
540 service_name=None, port='', service_type='ClusterIP'):
541 cmd = "kubectl -n {0} expose {1} {2} --port={3} --type={4}".format(
542 namespace, resource_type, resource_name, port, service_type)
543 if service_name is not None:
544 cmd += " --name={}".format(service_name)
545 return self._manager.controller_check_call(cmd)
Vladimir Jigulina6b018b2018-07-18 15:19:01 +0400546
Vladimir Jigulin4ad52a82018-08-12 05:51:30 +0400547 def expose(self, resource, service_name=None,
548 port='', service_type='ClusterIP'):
549 self.cli_expose(resource.namespace, resource.resource_type,
550 resource.name, service_name=service_name,
551 port=port, service_type=service_type)
552 return self._manager.api.services.get(
553 namespace=resource.namespace, name=service_name or resource.name)
554
555 def cli_exec(self, namespace, pod_name, cmd, container=''):
556 kubectl_cmd = "kubectl -n {0} exec --container={1} {2} -- {3}".format(
557 namespace, container, pod_name, cmd)
558 return self._manager.controller_check_call(kubectl_cmd)
559
560 # def exec(...), except exec is statement in python
561 def execute(self, pod, cmd, container=''):
562 return self.cli_exec(pod.namespace, pod.name, cmd, container=container)
563
564 def cli_annotate(self, namespace, resource_type, resource_name,
565 annotations, overwrite=False):
566 cmd = "kubectl -n {0} annotate {1} {2} {3}".format(
567 namespace, resource_type, resource_name, annotations)
568 if overwrite:
569 cmd += " --overwrite"
570 return self._manager.controller_check_call(cmd)
571
572 def annotate(self, resource, annotations, overwrite=False):
573 return self.cli_annotate(resource.namespace, resource.resource_type,
574 resource.name, annotations,
575 overwrite=overwrite)
576
577
578class K8SVirtlet(object):
579 """ Contain virtlet-related methods"""
580 def __init__(self, manager, namespace='kube-system'):
581 self._manager = manager
582 self._namespace = namespace
583
584 def get_virtlet_node_pod(self, node_name):
585 for pod in self._manager.api.pods.list(
586 namespace=self._namespace, name_prefix='virtlet-'):
587 if pod.read().spec.node_name == node_name:
588 return pod
589 return None
590
591 def get_pod_dom_uuid(self, pod):
592 uuid_name_map = self.virtlet_execute(
593 pod.read().spec.node_name, 'virsh list --uuid --name')['stdout']
Vladimir Jigulin4ad52a82018-08-12 05:51:30 +0400594 for line in uuid_name_map:
595 if line.rstrip().endswith("-{}".format(pod.name)):
596 return line.split(" ")[0]
597 raise Exception("Cannot detect uuid for pod {}".format(pod.name))
598
599 def virsh_domstats(self, pod):
600 """ get dict of vm stats """
601 uuid = self.get_pod_dom_uuid(pod)
602 result = self.virtlet_execute(
603 pod.read().spec.node_name, 'virsh domstats {}'.format(uuid))
604 stats = dict()
605 for line in result['stdout']:
606 if '=' in line:
607 vals = line.strip().split('=')
608 stats[vals[0]] = vals[1]
609 return stats
610
611 def virtlet_execute(self, node_name, cmd, container='libvirt'):
612 """ execute command inside virtlet container """
613 pod = self.get_virtlet_node_pod(node_name)
614 return self._manager.kubectl.execute(pod, cmd, container)
615
616
617class K8SSampleDeployment(object):
618 """ Wrapper for deployment run=>expose=>check frequent combination """
619 def __init__(self, manager, name,
620 namespace=None,
621 image='gcr.io/google-samples/node-hello:1.0',
622 port=8080,
623 replicas=2):
624 namespace = namespace or manager.api.default_namespace
625
626 self._manager = manager
627 self._port = port
628 self._deployment = \
629 manager.kubectl.run(namespace, name,
630 image=image, port=port, replicas=replicas)
631 self._index = 1 # used to generate svc name
632 self._svc = None # hold last created svc
633
634 def wait_ready(self, timeout=300, interval=5):
635 self._deployment.wait_ready(timeout=timeout, interval=interval)
636 return self
637
638 def svc(self):
639 """ Return the last exposed service"""
640 return self._svc
641
642 def expose(self, service_type='ClusterIP'):
643 service_name = "{0}-s{1}".format(self._deployment.name, self._index)
Vladimir Jigulin90689152018-09-26 15:38:19 +0400644 self._index += 1
Vladimir Jigulin4ad52a82018-08-12 05:51:30 +0400645 self._svc = self._manager.kubectl.expose(
646 self._deployment, port=self._port,
647 service_name=service_name, service_type=service_type)
648 return self._svc
649
650 def curl(self, svc=None, external=False):
651 if svc is None:
652 svc = self.svc()
653 url = "http://{0}:{1}".format(svc.get_ip(external), self._port)
Vladimir Jigulina6b018b2018-07-18 15:19:01 +0400654 if external:
655 return requests.get(url).text
656 else:
Vladimir Jigulin4ad52a82018-08-12 05:51:30 +0400657 return self._manager.curl(url)
Vladimir Jigulina6b018b2018-07-18 15:19:01 +0400658
Vladimir Jigulin4ad52a82018-08-12 05:51:30 +0400659 def is_service_available(self, svc=None, external=False):
660 return "Hello Kubernetes!" in self.curl(svc, external=external)
Vladimir Jigulin90689152018-09-26 15:38:19 +0400661
662 def delete(self):
663 for svc in self._manager.api.services.list_all(
664 name_prefix="{}-s".format(self._deployment.name)):
665 svc.delete()
666 self._deployment.delete()