blob: 0e57c89d8042128cc01a7e361d4009c3422cbcf8 [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
Vladimir Jigulin174aab12019-01-28 22:17:46 +040042 self._controller_name = None
Vladimir Jigulin4ad52a82018-08-12 05:51:30 +040043 self.kubectl = K8SKubectlCli(self)
44 self.virtlet = K8SVirtlet(self)
Victor Ryzhenkine784bbf2018-12-21 03:58:00 +040045 self.conformance_node = None
Vladimir Jigulin4ad52a82018-08-12 05:51:30 +040046 super(K8SManager, self).__init__(config=config, underlay=underlay)
Artem Panchenko0594cd72017-06-12 13:25:26 +030047
48 def install(self, commands):
49 self.execute_commands(commands,
50 label='Install Kubernetes services')
51 self.__config.k8s.k8s_installed = True
52 self.__config.k8s.kube_host = self.get_proxy_api()
53
54 def get_proxy_api(self):
55 k8s_proxy_ip_pillars = self._salt.get_pillar(
vrovachev99228d32017-06-08 19:46:10 +040056 tgt='I@haproxy:proxy:enabled:true and I@kubernetes:master',
Artem Panchenko0594cd72017-06-12 13:25:26 +030057 pillar='haproxy:proxy:listen:k8s_secure:binds:address')
vrovachev99228d32017-06-08 19:46:10 +040058 k8s_hosts = self._salt.get_pillar(
59 tgt='I@haproxy:proxy:enabled:true and I@kubernetes:master',
60 pillar='kubernetes:pool:apiserver:host')
Artem Panchenko0594cd72017-06-12 13:25:26 +030061 k8s_proxy_ip = set([ip
62 for item in k8s_proxy_ip_pillars
Dina Belovae6fdffb2017-09-19 13:58:34 -070063 for node, ip in item.items() if ip])
vrovachev99228d32017-06-08 19:46:10 +040064 k8s_hosts = set([ip
Dina Belovae6fdffb2017-09-19 13:58:34 -070065 for item in k8s_hosts
66 for node, ip in item.items() if ip])
vrovachev99228d32017-06-08 19:46:10 +040067 assert len(k8s_hosts) == 1, (
68 "Found more than one Kubernetes API hosts in pillars:{0}, "
69 "expected one!").format(k8s_hosts)
70 k8s_host = k8s_hosts.pop()
71 assert k8s_host in k8s_proxy_ip, (
72 "Kubernetes API host:{0} not found in proxies:{} "
73 "on k8s master nodes. K8s proxies are expected on "
74 "nodes with K8s master").format(k8s_host, k8s_proxy_ip)
75 return k8s_host
Artem Panchenko0594cd72017-06-12 13:25:26 +030076
Vladimir Jigulin4ad52a82018-08-12 05:51:30 +040077 def _api_init(self):
78 ca_result = self.controller_check_call(
79 'base64 --wrap=0 /etc/kubernetes/ssl/ca-kubernetes.crt')
80
81 self._api = cluster.K8sCluster(
82 user=self.__config.k8s_deploy.kubernetes_admin_user,
83 password=self.__config.k8s_deploy.kubernetes_admin_password,
84 ca=ca_result['stdout'][0],
85 host=self.__config.k8s.kube_host,
86 port=self.__config.k8s.kube_apiserver_port)
87
Artem Panchenko0594cd72017-06-12 13:25:26 +030088 @property
89 def api(self):
Vladimir Jigulin4ad52a82018-08-12 05:51:30 +040090 """
91 :rtype: cluster.K8sCluster
92 """
93 if self._api is None:
94 self._api_init()
95 return self._api
Artem Panchenko0594cd72017-06-12 13:25:26 +030096
Vladimir Jigulin4ad52a82018-08-12 05:51:30 +040097 def get_controllers(self):
98 """ Return list of controllers ssh underlays """
Vladimir Jigulin34dfa942018-07-23 21:05:48 +040099 return [node for node in self.__config.underlay.ssh if
100 ext.UNDERLAY_NODE_ROLES.k8s_controller in node['roles']]
101
Vladimir Jigulin4ad52a82018-08-12 05:51:30 +0400102 def get_masters(self):
103 """ Return list of kubernetes masters hosts fqdn """
104 masters_fqdn = self._salt.get_pillar(
105 tgt='I@kubernetes:master', pillar='linux:network:fqdn')
106 return [self.__underlay.host_by_node_name(node_name=v)
107 for pillar in masters_fqdn for k, v in pillar.items()]
108
Vladimir Jigulin174aab12019-01-28 22:17:46 +0400109 def renew_controller(self, controller_node_name=None):
110 """ Changes controller returned by controller_name property """
111 if controller_node_name is not None:
112 self._controller_name = controller_node_name
113 return self._controller_name
114 names = [node['node_name'] for node in self.get_controllers()]
115 if len(names) == 1 and names[0] == self._controller_name:
116 LOG.warn(
117 "Cannot change controller because there is only 1 of them")
118 return
119 else:
120 for name in names:
121 if name != self._controller_name:
122 self._controller_name = name
123 return
124
Victor Ryzhenkin66d39372017-09-28 19:25:48 +0400125 @property
Vladimir Jigulin4ad52a82018-08-12 05:51:30 +0400126 def controller_name(self):
127 """ Return node name of controller node that used for all actions """
Vladimir Jigulin174aab12019-01-28 22:17:46 +0400128 if self._controller_name is None:
129 self.renew_controller()
130 return self._controller_name
Victor Ryzhenkin66d39372017-09-28 19:25:48 +0400131
Dennis Dmitriev66650fc2018-11-02 11:04:37 +0200132 @property
133 def controller_minion_id(self):
134 """ Return node name of controller node that used for all actions """
135 minion_ids = [minion_id['minion_id'] for minion_id in
136 self.get_controllers()]
137 # we want to return same controller name every time
138 minion_ids.sort()
139 return minion_ids[0]
140
141 @property
142 def is_metallb_enabled(self):
143 ctl_tgt = self.controller_minion_id
144 LOG.debug("Controller target: {}".format(ctl_tgt))
145
146 result = self._salt.get_pillar(
147 tgt=ctl_tgt,
148 pillar='kubernetes:common:addons:metallb:enabled')
149 metallb = result[0].get(ctl_tgt, False)
150 LOG.info("{} kubernetes:common:addons:metallb:enabled: {}"
151 .format(ctl_tgt, bool(metallb)))
152 return metallb
153
154 @property
155 def is_ingress_nginx_enabled(self):
156 ctl_tgt = self.controller_minion_id
157 LOG.debug("Controller target: {}".format(ctl_tgt))
158
159 result = self._salt.get_pillar(
160 tgt=ctl_tgt,
161 pillar='kubernetes:common:addons:ingress-nginx:enabled')
162 ingress_nginx = result[0].get(ctl_tgt, False)
163 LOG.info("{} kubernetes:common:addons:ingress-nginx:enabled: {}"
164 .format(ctl_tgt, bool(ingress_nginx)))
165 return ingress_nginx
166
Vladimir Jigulin4ad52a82018-08-12 05:51:30 +0400167 def controller_check_call(self, cmd, **kwargs):
168 """ Run command on controller and return result """
169 LOG.info("running cmd on k8s controller: {}".format(cmd))
Vladimir Jigulinee1faa52018-06-25 13:00:51 +0400170 return self.__underlay.check_call(
Vladimir Jigulin4ad52a82018-08-12 05:51:30 +0400171 cmd=cmd, node_name=self.controller_name, **kwargs)
Artem Panchenko501e67e2017-06-14 14:59:18 +0300172
Vladimir Jigulin4ad52a82018-08-12 05:51:30 +0400173 def get_keepalived_vip(self):
Vladimir Jigulin62bcf462018-05-28 18:17:01 +0400174 """
Vladimir Jigulin4ad52a82018-08-12 05:51:30 +0400175 Return k8s VIP IP address
Vladimir Jigulin62bcf462018-05-28 18:17:01 +0400176
Vladimir Jigulin4ad52a82018-08-12 05:51:30 +0400177 :return: str, IP address
Vladimir Jigulin62bcf462018-05-28 18:17:01 +0400178 """
Vladimir Jigulin4ad52a82018-08-12 05:51:30 +0400179 ctl_vip_pillar = self._salt.get_pillar(
Vladimir Jigulinc69f4ba2019-01-14 15:27:44 +0400180 tgt="I@kubernetes:control",
Vladimir Jigulin4ad52a82018-08-12 05:51:30 +0400181 pillar="_param:cluster_vip_address")[0]
182 return ctl_vip_pillar.values()[0]
Vladimir Jigulin62bcf462018-05-28 18:17:01 +0400183
Vladimir Jigulin4ad52a82018-08-12 05:51:30 +0400184 def run_sample_deployment(self, name, **kwargs):
185 return K8SSampleDeployment(self, name, **kwargs)
Victor Ryzhenkin3ffa2b42017-10-05 16:38:44 +0400186
Vladimir Jigulin4ad52a82018-08-12 05:51:30 +0400187 def get_pod_ips_from_container(self, pod_name, exclude_local=True,
188 namespace='default'):
189 """ Get ips from container using 'ip a'
190 Required for cni-genie multi-cni cases
191
192 :return: list of IP adresses
Victor Ryzhenkin3ffa2b42017-10-05 16:38:44 +0400193 """
Vladimir Jigulin4ad52a82018-08-12 05:51:30 +0400194 cmd = "ip a|grep \"inet \"|awk '{{print $2}}'"
195 result = self.kubectl.cli_exec(namespace, pod_name, cmd)['stdout']
196 ips = [line.strip().split('/')[0] for line in result]
197 if exclude_local:
198 ips = [ip for ip in ips if not ip.startswith("127.")]
199 return ips
Victor Ryzhenkin3ffa2b42017-10-05 16:38:44 +0400200
Vladimir Jigulin4ad52a82018-08-12 05:51:30 +0400201 def update_k8s_version(self, tag):
Vladimir Jigulin62bcf462018-05-28 18:17:01 +0400202 """
203 Update k8s images tag version in cluster meta and apply required
204 for update states
205
206 :param tag: New version tag of k8s images
207 :return:
208 """
209 master_host = self.__config.salt.salt_master_host
210
211 def update_image_tag_meta(config, image_name):
212 image_old = config.get(image_name)
213 image_base = image_old.split(':')[0]
214 image_new = "{}:{}".format(image_base, tag)
215 LOG.info("Changing k8s '{0}' image cluster meta to '{1}'".format(
216 image_name, image_new))
217
218 with self.__underlay.remote(host=master_host) as r:
219 cmd = "salt-call reclass.cluster_meta_set" \
220 " name={0} value={1}".format(image_name, image_new)
221 r.check_call(cmd)
222 return image_new
223
224 cfg = self.__config
225
226 update_image_tag_meta(cfg.k8s_deploy, "kubernetes_hyperkube_image")
227 update_image_tag_meta(cfg.k8s_deploy, "kubernetes_pause_image")
228 cfg.k8s.k8s_conformance_image = update_image_tag_meta(
229 cfg.k8s, "k8s_conformance_image")
230
231 steps_path = cfg.k8s_deploy.k8s_update_steps_path
232 update_commands = self.__underlay.read_template(steps_path)
233 self.execute_commands(
234 update_commands, label="Updating kubernetes to '{}'".format(tag))
Vladimir Jigulinee1faa52018-06-25 13:00:51 +0400235
Vladimir Jigulin4ad52a82018-08-12 05:51:30 +0400236 def run_conformance(self, timeout=60*60, log_out='k8s_conformance.log',
237 raise_on_err=True, node_name=None,
238 api_server='http://127.0.0.1:8080'):
239 if node_name is None:
240 node_name = self.controller_name
241 cmd = "set -o pipefail; docker run --net=host " \
242 "-e API_SERVER='{api}' {image} | tee '{log}'".format(
243 api=api_server, log=log_out,
244 image=self.__config.k8s.k8s_conformance_image)
245 return self.__underlay.check_call(
246 cmd=cmd, node_name=node_name, timeout=timeout,
Dennis Dmitrieveb50ce12018-09-27 13:34:32 +0300247 raise_on_err=raise_on_err, verbose=True)
Vladimir Jigulin4ad52a82018-08-12 05:51:30 +0400248
249 def run_virtlet_conformance(self, timeout=60 * 120,
Dennis Dmitriev34fd3002018-11-15 18:25:16 +0200250 log_file='virtlet_conformance.log',
251 report_name="report.xml"):
Vladimir Jigulin4ad52a82018-08-12 05:51:30 +0400252 if self.__config.k8s.run_extended_virtlet_conformance:
253 ci_image = "cloud-images.ubuntu.com/xenial/current/" \
254 "xenial-server-cloudimg-amd64-disk1.img"
255 cmd = ("set -o pipefail; "
256 "docker run --net=host {0} /virtlet-e2e-tests "
Dennis Dmitriev34fd3002018-11-15 18:25:16 +0200257 "-include-cloud-init-tests -junitOutput {3} "
Vladimir Jigulin4ad52a82018-08-12 05:51:30 +0400258 "-image {2} -sshuser ubuntu -memoryLimit 1024 "
259 "-alsologtostderr -cluster-url http://127.0.0.1:8080 "
260 "-ginkgo.focus '\[Conformance\]' "
261 "| tee {1}".format(
262 self.__config.k8s_deploy.kubernetes_virtlet_image,
Dennis Dmitriev34fd3002018-11-15 18:25:16 +0200263 log_file, ci_image, report_name))
Vladimir Jigulin4ad52a82018-08-12 05:51:30 +0400264 else:
265 cmd = ("set -o pipefail; "
266 "docker run --net=host {0} /virtlet-e2e-tests "
Dennis Dmitriev34fd3002018-11-15 18:25:16 +0200267 "-junitOutput {2} "
Vladimir Jigulin4ad52a82018-08-12 05:51:30 +0400268 "-alsologtostderr -cluster-url http://127.0.0.1:8080 "
269 "-ginkgo.focus '\[Conformance\]' "
270 "| tee {1}".format(
271 self.__config.k8s_deploy.kubernetes_virtlet_image,
Dennis Dmitriev34fd3002018-11-15 18:25:16 +0200272 log_file, report_name))
Vladimir Jigulin4ad52a82018-08-12 05:51:30 +0400273 LOG.info("Executing: {}".format(cmd))
274 with self.__underlay.remote(
275 node_name=self.controller_name) as remote:
276 result = remote.check_call(cmd, timeout=timeout)
277 stderr = result['stderr']
278 stdout = result['stdout']
279 LOG.info("Test results stdout: {}".format(stdout))
280 LOG.info("Test results stderr: {}".format(stderr))
281 return result
282
Vladimir Jigulin51611672018-11-23 03:10:22 +0400283 def start_k8s_cncf_verification(self, timeout=60 * 180):
Vladimir Jigulin0c8dd5a2018-08-28 05:08:35 +0400284 """
285 Build sonobuoy using golang docker image and install it in system
286 Then generate sonobuoy verification manifest using gen command
287 and wait for it to end using status annotation
288 TODO (vjigulin): All sonobuoy methods can be improved if we define
289 correct KUBECONFIG env variable
290 """
291 cncf_cmd =\
292 'docker run --network host --name sonobuoy golang:1.11 bash -c ' \
293 '"go get -d -v github.com/heptio/sonobuoy && ' \
294 'go install -v github.com/heptio/sonobuoy" && ' \
295 'docker cp sonobuoy:/go/bin/sonobuoy /usr/local/bin/ && ' \
296 'docker rm sonobuoy && ' \
297 'sonobuoy gen | kubectl apply -f -'
Vladimir Jigulin4ad52a82018-08-12 05:51:30 +0400298
Vladimir Jigulin0c8dd5a2018-08-28 05:08:35 +0400299 self.controller_check_call(cncf_cmd, timeout=900)
300
301 sonobuoy_pod = self.api.pods.get('sonobuoy', 'heptio-sonobuoy')
302 sonobuoy_pod.wait_running()
303
304 def sonobuoy_status():
305 annotations = sonobuoy_pod.read().metadata.annotations
306 json_status = annotations['sonobuoy.hept.io/status']
307 status = yaml.safe_load(json_status)['status']
308 if status != 'running':
309 LOG.info("CNCF status: {}".format(json_status))
310 return yaml.safe_load(json_status)['status']
311
312 LOG.info("Waiting for CNCF to complete")
313 helpers.wait(
314 lambda: sonobuoy_status() == 'complete',
Vladimir Jigulin51611672018-11-23 03:10:22 +0400315 interval=120, timeout=timeout,
Vladimir Jigulin0c8dd5a2018-08-28 05:08:35 +0400316 timeout_msg="Timeout for CNCF reached."
317 )
Vladimir Jigulin4ad52a82018-08-12 05:51:30 +0400318
Victor Ryzhenkin57c43202018-12-28 01:48:39 +0400319 def determine_conformance_node(self, target):
320 masters_fqdn = self._salt.get_pillar(
321 tgt='I@kubernetes:master', pillar='linux:network:fqdn')
322 node_names = [v for pillar in masters_fqdn for
323 k, v in pillar.items()]
324 return [node_name for node_name
325 in node_names
326 if node_name.startswith(target)][0]
327
Victor Ryzhenkine784bbf2018-12-21 03:58:00 +0400328 def start_conformance_inside_pod(self, cnf_type='k8s', timeout=60 * 60):
329 """
330 Create conformance pod and wait for results
331 :param cnf_type: k8s or virtlet. choose what conformance you want
332 :param timeout:
333 :return:
334 """
335 if cnf_type == 'k8s':
336 pod_mark = 'conformance'
337 elif cnf_type == 'virtlet':
338 pod_mark = 'virtlet-conformance'
339 else:
340 LOG.error("Unknown conformance type or it even not set")
341 raise RuntimeError("Unknown conformance type")
342 conformance_cmd = "kubectl apply -f /srv/kubernetes/{}.yml" \
343 "".format(pod_mark)
344 self.controller_check_call(conformance_cmd, timeout=900)
345
346 cnf_pod = self.api.pods.get(pod_mark, pod_mark)
347 cnf_pod.wait_running()
348
349 pod = cnf_pod.read()
350 target = "{}.".format(pod.spec.node_name)
Victor Ryzhenkin57c43202018-12-28 01:48:39 +0400351
352 self.conformance_node = self.determine_conformance_node(target)
Victor Ryzhenkine784bbf2018-12-21 03:58:00 +0400353
354 def cnf_status():
355 pod = cnf_pod.read()
356 status = pod.status.phase
357 LOG.info("Conformance status: {}".format(status))
358 return status
359
360 LOG.info("Waiting for Conformance to complete")
361 helpers.wait(
Aleksei Kasatkin84cc1e22019-01-18 16:55:51 +0100362 lambda: cnf_status() in ('Succeeded', 'Failed'),
Victor Ryzhenkine784bbf2018-12-21 03:58:00 +0400363 interval=120, timeout=timeout,
364 timeout_msg="Timeout for Conformance reached."
365 )
366
367 pod = cnf_pod.read()
368 status = pod.status.phase
Aleksei Kasatkin75a08032019-01-21 11:51:35 +0100369 if status == 'Failed':
Victor Ryzhenkine784bbf2018-12-21 03:58:00 +0400370 describe = "kubectl describe po {0} -n {0}".format(pod_mark)
371 LOG.info(self.controller_check_call(describe, timeout=30))
372 raise RuntimeError("Conformance failed")
373
Tatyana Leontovich619c3362019-01-04 02:17:26 +0200374 def get_node_name(self, tgt):
375 res = [node_name for node_name in
376 self.__underlay.node_names() if tgt in node_name]
377 assert len(res) > 0, 'Can not find node name by tgt {}'.format(tgt)
378 return res[0]
379
Victor Ryzhenkine784bbf2018-12-21 03:58:00 +0400380 def move_file_to_root_folder(self, filepath):
Tatyana Leontovich619c3362019-01-04 02:17:26 +0200381 cmd = "mv {0} /root/".format(filepath)
Victor Ryzhenkine784bbf2018-12-21 03:58:00 +0400382 if self.conformance_node:
Tatyana Leontovich619c3362019-01-04 02:17:26 +0200383 short_name = self.conformance_node.split('.')[0]
Victor Ryzhenkin95046882018-12-29 19:18:40 +0400384 LOG.info("Managing results on {}".format(self.conformance_node))
Tatyana Leontovich619c3362019-01-04 02:17:26 +0200385 step = {'cmd': cmd, 'node_name': self.get_node_name(
386 tgt=short_name)}
387 LOG.info('Move {0} to /root/ on {1}'.format(
388 filepath, self.conformance_node))
389 self.execute_command(step, 'Move files')
Victor Ryzhenkine784bbf2018-12-21 03:58:00 +0400390 else:
391 LOG.info("Node is not properly set")
392
Vladimir Jigulin4ad52a82018-08-12 05:51:30 +0400393 def extract_file_to_node(self, system='docker',
394 container='virtlet',
395 file_path='report.xml',
396 out_dir='.',
397 **kwargs):
Vladimir Jigulinee1faa52018-06-25 13:00:51 +0400398 """
Vladimir Jigulin4ad52a82018-08-12 05:51:30 +0400399 Download file from docker or k8s container to node
Vladimir Jigulinee1faa52018-06-25 13:00:51 +0400400
Vladimir Jigulin4ad52a82018-08-12 05:51:30 +0400401 :param system: docker or k8s
402 :param container: Full name of part of name
403 :param file_path: File path in container
404 :param kwargs: Used to control pod and namespace
405 :param out_dir: Output directory
406 :return:
Vladimir Jigulinee1faa52018-06-25 13:00:51 +0400407 """
Vladimir Jigulin0c8dd5a2018-08-28 05:08:35 +0400408 with self.__underlay.remote(node_name=self.controller_name) as remote:
Aleksei Kasatkin75a08032019-01-21 11:51:35 +0100409 if system == 'docker':
Vladimir Jigulin4ad52a82018-08-12 05:51:30 +0400410 cmd = ("docker ps --all | grep \"{0}\" |"
411 " awk '{{print $1}}'".format(container))
412 result = remote.check_call(cmd, raise_on_err=False)
413 if result['stdout']:
414 container_id = result['stdout'][0].strip()
415 else:
416 LOG.info('No container found, skipping extraction...')
417 return
418 cmd = "docker start {}".format(container_id)
419 remote.check_call(cmd, raise_on_err=False)
420 cmd = "docker cp \"{0}:/{1}\" \"{2}\"".format(
421 container_id, file_path, out_dir)
422 remote.check_call(cmd, raise_on_err=False)
423 else:
424 # system is k8s
425 pod_name = kwargs.get('pod_name')
426 pod_namespace = kwargs.get('pod_namespace')
427 cmd = 'kubectl cp \"{0}/{1}:/{2}\" \"{3}\"'.format(
428 pod_namespace, pod_name, file_path, out_dir)
429 remote.check_call(cmd, raise_on_err=False)
Vladimir Jigulina6b018b2018-07-18 15:19:01 +0400430
Vladimir Jigulin4ad52a82018-08-12 05:51:30 +0400431 def download_k8s_logs(self, files):
432 """
433 Download JUnit report and conformance logs from cluster
434 :param files:
435 :return:
436 """
Victor Ryzhenkine784bbf2018-12-21 03:58:00 +0400437 if self.conformance_node:
438 node = self.conformance_node
439 else:
440 node = self.controller_name
441 LOG.info("Trying to get logs at {}".format(node))
Vladimir Jigulin4ad52a82018-08-12 05:51:30 +0400442 master_host = self.__config.salt.salt_master_host
443 with self.__underlay.remote(host=master_host) as r:
444 for log_file in files:
Tatyana Leontovich619c3362019-01-04 02:17:26 +0200445 cmd = "scp -r \"{0}:/root/{1}\" /root/".format(
Victor Ryzhenkine784bbf2018-12-21 03:58:00 +0400446 node, log_file)
Tatyana Leontovich48a33c12019-01-03 02:19:25 +0200447 r.check_call(cmd, raise_on_err=True)
Vladimir Jigulin4ad52a82018-08-12 05:51:30 +0400448 LOG.info("Downloading the artifact {0}".format(log_file))
449 r.download(destination=log_file, target=os.getcwd())
Dennis Dmitriev445b4322018-10-08 14:32:07 +0300450 self.store_server_version(os.path.join(os.getcwd(), 'env_k8s_version'))
Vladimir Jigulina6b018b2018-07-18 15:19:01 +0400451
Vladimir Jigulin4ad52a82018-08-12 05:51:30 +0400452 def combine_xunit(self, path, output):
453 """
454 Function to combine multiple xmls with test results to
455 one.
Vladimir Jigulin34dfa942018-07-23 21:05:48 +0400456
Vladimir Jigulin4ad52a82018-08-12 05:51:30 +0400457 :param path: Path where xmls to combine located
458 :param output: Path to xml file where output will stored
459 :return:
460 """
Victor Ryzhenkine784bbf2018-12-21 03:58:00 +0400461 if self.conformance_node:
462 node = self.conformance_node
463 else:
464 node = self.controller_name
465 LOG.info("Trying to combine xunit at {}".format(node))
Victor Ryzhenkin95046882018-12-29 19:18:40 +0400466 cmd = ("apt-get install python-setuptools -y; "
467 "pip install "
468 "https://github.com/mogaika/xunitmerge/archive/master.zip")
469 LOG.debug('Installing xunitmerge')
470 self._salt.cmd_run(tgt=node, cmd=cmd)
471 LOG.debug('Merging xunit')
472 cmd = ("cd {0}; arg=''; "
473 "for i in $(ls | grep xml); "
474 "do arg=\"$arg $i\"; done && "
475 "xunitmerge $arg {1} || true".format(path, output))
476 self._salt.cmd_run(tgt=node, cmd=cmd)
Vladimir Jigulin34dfa942018-07-23 21:05:48 +0400477
Vladimir Jigulin4ad52a82018-08-12 05:51:30 +0400478 def manage_cncf_archive(self):
479 """
Vladimir Jigulin0c8dd5a2018-08-28 05:08:35 +0400480 Function to untar archive, move files that we are needs to the
481 home folder, prepare it to downloading.
482 Will generate files on controller node:
483 e2e.log, junit_01.xml, cncf_results.tar.gz, version.txt
Vladimir Jigulin4ad52a82018-08-12 05:51:30 +0400484 :return:
485 """
486
Vladimir Jigulin0c8dd5a2018-08-28 05:08:35 +0400487 cmd =\
488 "rm -rf cncf_results.tar.gz result && " \
489 "mkdir result && " \
490 "mv *_sonobuoy_*.tar.gz cncf_results.tar.gz && " \
491 "tar -C result -xzf cncf_results.tar.gz && " \
492 "mv result/plugins/e2e/results/e2e.log . ; " \
493 "mv result/plugins/e2e/results/junit_01.xml . ; " \
494 "kubectl version > version.txt"
Vladimir Jigulin4ad52a82018-08-12 05:51:30 +0400495
Vladimir Jigulin0c8dd5a2018-08-28 05:08:35 +0400496 self.controller_check_call(cmd, raise_on_err=False)
Vladimir Jigulin4ad52a82018-08-12 05:51:30 +0400497
Vladimir Jigulin174aab12019-01-28 22:17:46 +0400498 @retry(100, exception=DevopsCalledProcessError, interval=20)
Vladimir Jigulin4ad52a82018-08-12 05:51:30 +0400499 def nslookup(self, host, src):
500 """ Run nslookup on controller and return result """
501 return self.controller_check_call("nslookup {0} {1}".format(host, src))
502
Vladimir Jigulin174aab12019-01-28 22:17:46 +0400503 @retry(100, exception=DevopsCalledProcessError, interval=20)
Vladimir Jigulin57ecae92018-09-10 22:51:15 +0400504 def curl(self, url, *args):
Vladimir Jigulin4ad52a82018-08-12 05:51:30 +0400505 """
506 Run curl on controller and return stdout
507
508 :param url: url to curl
Vladimir Jigulin57ecae92018-09-10 22:51:15 +0400509 :return: list of strings (with /n at end of every line)
Vladimir Jigulin4ad52a82018-08-12 05:51:30 +0400510 """
Vladimir Jigulin57ecae92018-09-10 22:51:15 +0400511 args = list(args)
512 args.append(url)
513 cmd = "curl -s -S {}".format(
514 " ".join(["'{}'".format(a.replace("'", "\\'")) for a in args]))
515 result = self.controller_check_call(cmd)
516 LOG.debug("{0}\nresult:\n{1}".format(cmd, result['stdout']))
Vladimir Jigulin4ad52a82018-08-12 05:51:30 +0400517 return result['stdout']
Vladimir Jigulin34dfa942018-07-23 21:05:48 +0400518
Dennis Dmitriev445b4322018-10-08 14:32:07 +0300519 def store_server_version(self, env_file_path):
520 """Store Kubernetes server version in bash source file"""
521
522 def digits(string):
523 return ''.join(n for n in string if n.isdigit())
524
525 ver = self.api.api_version.get_code()
526 LOG.debug("Got Kubernetes server version:\n{0}".format(ver))
527
528 env_version = ("export KUBE_SERVER_VERSION={0}.{1}\n"
529 "export KUBE_SERVER_GIT_VERSION={2}\n"
530 .format(digits(ver.major),
531 digits(ver.minor),
532 ver.git_version))
533
534 LOG.info("Kubernetes server version is stored to {0}:\n{1}"
535 .format(env_file_path, env_version))
536 with open(env_file_path, 'w') as kver:
537 kver.write(env_version)
538
Vladimir Jigulina6b018b2018-07-18 15:19:01 +0400539
Vladimir Jigulin4ad52a82018-08-12 05:51:30 +0400540class K8SKubectlCli(object):
541 """ Contain kubectl cli commands and api wrappers"""
542 def __init__(self, manager):
543 self._manager = manager
Vladimir Jigulina6b018b2018-07-18 15:19:01 +0400544
Vladimir Jigulin4ad52a82018-08-12 05:51:30 +0400545 def cli_run(self, namespace, name, image, port, replicas=1):
546 cmd = "kubectl -n {0} run {1} --image={2} --port={3} --replicas={4}".\
547 format(namespace, name, image, port, replicas)
548 return self._manager.controller_check_call(cmd)
Vladimir Jigulina6b018b2018-07-18 15:19:01 +0400549
Vladimir Jigulin4ad52a82018-08-12 05:51:30 +0400550 def run(self, namespace, name, image, port, replicas=1):
551 self.cli_run(namespace, name, image, port, replicas)
552 return self._manager.api.deployments.get(
553 namespace=namespace, name=name)
Vladimir Jigulina6b018b2018-07-18 15:19:01 +0400554
Vladimir Jigulin4ad52a82018-08-12 05:51:30 +0400555 def cli_expose(self, namespace, resource_type, resource_name,
556 service_name=None, port='', service_type='ClusterIP'):
557 cmd = "kubectl -n {0} expose {1} {2} --port={3} --type={4}".format(
558 namespace, resource_type, resource_name, port, service_type)
559 if service_name is not None:
560 cmd += " --name={}".format(service_name)
561 return self._manager.controller_check_call(cmd)
Vladimir Jigulina6b018b2018-07-18 15:19:01 +0400562
Vladimir Jigulin4ad52a82018-08-12 05:51:30 +0400563 def expose(self, resource, service_name=None,
564 port='', service_type='ClusterIP'):
565 self.cli_expose(resource.namespace, resource.resource_type,
566 resource.name, service_name=service_name,
567 port=port, service_type=service_type)
568 return self._manager.api.services.get(
569 namespace=resource.namespace, name=service_name or resource.name)
570
571 def cli_exec(self, namespace, pod_name, cmd, container=''):
572 kubectl_cmd = "kubectl -n {0} exec --container={1} {2} -- {3}".format(
573 namespace, container, pod_name, cmd)
574 return self._manager.controller_check_call(kubectl_cmd)
575
576 # def exec(...), except exec is statement in python
577 def execute(self, pod, cmd, container=''):
578 return self.cli_exec(pod.namespace, pod.name, cmd, container=container)
579
580 def cli_annotate(self, namespace, resource_type, resource_name,
581 annotations, overwrite=False):
582 cmd = "kubectl -n {0} annotate {1} {2} {3}".format(
583 namespace, resource_type, resource_name, annotations)
584 if overwrite:
585 cmd += " --overwrite"
586 return self._manager.controller_check_call(cmd)
587
588 def annotate(self, resource, annotations, overwrite=False):
589 return self.cli_annotate(resource.namespace, resource.resource_type,
590 resource.name, annotations,
591 overwrite=overwrite)
592
593
594class K8SVirtlet(object):
595 """ Contain virtlet-related methods"""
596 def __init__(self, manager, namespace='kube-system'):
597 self._manager = manager
598 self._namespace = namespace
599
600 def get_virtlet_node_pod(self, node_name):
601 for pod in self._manager.api.pods.list(
602 namespace=self._namespace, name_prefix='virtlet-'):
603 if pod.read().spec.node_name == node_name:
604 return pod
605 return None
606
607 def get_pod_dom_uuid(self, pod):
608 uuid_name_map = self.virtlet_execute(
609 pod.read().spec.node_name, 'virsh list --uuid --name')['stdout']
Vladimir Jigulin4ad52a82018-08-12 05:51:30 +0400610 for line in uuid_name_map:
611 if line.rstrip().endswith("-{}".format(pod.name)):
612 return line.split(" ")[0]
613 raise Exception("Cannot detect uuid for pod {}".format(pod.name))
614
615 def virsh_domstats(self, pod):
616 """ get dict of vm stats """
617 uuid = self.get_pod_dom_uuid(pod)
618 result = self.virtlet_execute(
619 pod.read().spec.node_name, 'virsh domstats {}'.format(uuid))
620 stats = dict()
621 for line in result['stdout']:
622 if '=' in line:
623 vals = line.strip().split('=')
624 stats[vals[0]] = vals[1]
625 return stats
626
627 def virtlet_execute(self, node_name, cmd, container='libvirt'):
628 """ execute command inside virtlet container """
629 pod = self.get_virtlet_node_pod(node_name)
630 return self._manager.kubectl.execute(pod, cmd, container)
631
632
633class K8SSampleDeployment(object):
634 """ Wrapper for deployment run=>expose=>check frequent combination """
635 def __init__(self, manager, name,
636 namespace=None,
637 image='gcr.io/google-samples/node-hello:1.0',
638 port=8080,
639 replicas=2):
640 namespace = namespace or manager.api.default_namespace
641
642 self._manager = manager
643 self._port = port
644 self._deployment = \
645 manager.kubectl.run(namespace, name,
646 image=image, port=port, replicas=replicas)
647 self._index = 1 # used to generate svc name
648 self._svc = None # hold last created svc
649
650 def wait_ready(self, timeout=300, interval=5):
651 self._deployment.wait_ready(timeout=timeout, interval=interval)
652 return self
653
654 def svc(self):
655 """ Return the last exposed service"""
656 return self._svc
657
658 def expose(self, service_type='ClusterIP'):
659 service_name = "{0}-s{1}".format(self._deployment.name, self._index)
Vladimir Jigulin90689152018-09-26 15:38:19 +0400660 self._index += 1
Vladimir Jigulin4ad52a82018-08-12 05:51:30 +0400661 self._svc = self._manager.kubectl.expose(
662 self._deployment, port=self._port,
663 service_name=service_name, service_type=service_type)
664 return self._svc
665
666 def curl(self, svc=None, external=False):
667 if svc is None:
668 svc = self.svc()
669 url = "http://{0}:{1}".format(svc.get_ip(external), self._port)
Vladimir Jigulina6b018b2018-07-18 15:19:01 +0400670 if external:
671 return requests.get(url).text
672 else:
Vladimir Jigulin4ad52a82018-08-12 05:51:30 +0400673 return self._manager.curl(url)
Vladimir Jigulina6b018b2018-07-18 15:19:01 +0400674
Vladimir Jigulin4ad52a82018-08-12 05:51:30 +0400675 def is_service_available(self, svc=None, external=False):
676 return "Hello Kubernetes!" in self.curl(svc, external=external)
Vladimir Jigulin90689152018-09-26 15:38:19 +0400677
678 def delete(self):
679 for svc in self._manager.api.services.list_all(
680 name_prefix="{}-s".format(self._deployment.name)):
681 svc.delete()
682 self._deployment.delete()