blob: 7b03fe9a759cad437a74afbc9dad479064a63444 [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 """
111 names = [node['node_name'] for node in self.get_controllers()]
112 # 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(
164 tgt="I@kubernetes:control:enabled:True",
165 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(
346 lambda: cnf_status() == ('Succeeded' or 'Failed'),
347 interval=120, timeout=timeout,
348 timeout_msg="Timeout for Conformance reached."
349 )
350
351 pod = cnf_pod.read()
352 status = pod.status.phase
353 if status is 'Failed':
354 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
358 def move_file_to_root_folder(self, filepath):
Victor Ryzhenkin95046882018-12-29 19:18:40 +0400359 # Using || true to avoid salt fails if no file found
360 cmd = "mv {0} /root/ || true".format(filepath)
Victor Ryzhenkine784bbf2018-12-21 03:58:00 +0400361 if self.conformance_node:
Victor Ryzhenkin95046882018-12-29 19:18:40 +0400362 LOG.info("Managing results on {}".format(self.conformance_node))
363 self._salt.cmd_run(tgt=self.conformance_node, cmd=cmd)
Victor Ryzhenkine784bbf2018-12-21 03:58:00 +0400364 else:
365 LOG.info("Node is not properly set")
366
Vladimir Jigulin4ad52a82018-08-12 05:51:30 +0400367 def extract_file_to_node(self, system='docker',
368 container='virtlet',
369 file_path='report.xml',
370 out_dir='.',
371 **kwargs):
Vladimir Jigulinee1faa52018-06-25 13:00:51 +0400372 """
Vladimir Jigulin4ad52a82018-08-12 05:51:30 +0400373 Download file from docker or k8s container to node
Vladimir Jigulinee1faa52018-06-25 13:00:51 +0400374
Vladimir Jigulin4ad52a82018-08-12 05:51:30 +0400375 :param system: docker or k8s
376 :param container: Full name of part of name
377 :param file_path: File path in container
378 :param kwargs: Used to control pod and namespace
379 :param out_dir: Output directory
380 :return:
Vladimir Jigulinee1faa52018-06-25 13:00:51 +0400381 """
Vladimir Jigulin0c8dd5a2018-08-28 05:08:35 +0400382 with self.__underlay.remote(node_name=self.controller_name) as remote:
Vladimir Jigulin4ad52a82018-08-12 05:51:30 +0400383 if system is 'docker':
384 cmd = ("docker ps --all | grep \"{0}\" |"
385 " awk '{{print $1}}'".format(container))
386 result = remote.check_call(cmd, raise_on_err=False)
387 if result['stdout']:
388 container_id = result['stdout'][0].strip()
389 else:
390 LOG.info('No container found, skipping extraction...')
391 return
392 cmd = "docker start {}".format(container_id)
393 remote.check_call(cmd, raise_on_err=False)
394 cmd = "docker cp \"{0}:/{1}\" \"{2}\"".format(
395 container_id, file_path, out_dir)
396 remote.check_call(cmd, raise_on_err=False)
397 else:
398 # system is k8s
399 pod_name = kwargs.get('pod_name')
400 pod_namespace = kwargs.get('pod_namespace')
401 cmd = 'kubectl cp \"{0}/{1}:/{2}\" \"{3}\"'.format(
402 pod_namespace, pod_name, file_path, out_dir)
403 remote.check_call(cmd, raise_on_err=False)
Vladimir Jigulina6b018b2018-07-18 15:19:01 +0400404
Vladimir Jigulin4ad52a82018-08-12 05:51:30 +0400405 def download_k8s_logs(self, files):
406 """
407 Download JUnit report and conformance logs from cluster
408 :param files:
409 :return:
410 """
Victor Ryzhenkine784bbf2018-12-21 03:58:00 +0400411 if self.conformance_node:
412 node = self.conformance_node
413 else:
414 node = self.controller_name
415 LOG.info("Trying to get logs at {}".format(node))
Vladimir Jigulin4ad52a82018-08-12 05:51:30 +0400416 master_host = self.__config.salt.salt_master_host
417 with self.__underlay.remote(host=master_host) as r:
418 for log_file in files:
419 cmd = "rsync -r \"{0}:/root/{1}\" /root/".format(
Victor Ryzhenkine784bbf2018-12-21 03:58:00 +0400420 node, log_file)
Tatyana Leontovich48a33c12019-01-03 02:19:25 +0200421 r.check_call(cmd, raise_on_err=True)
Vladimir Jigulin4ad52a82018-08-12 05:51:30 +0400422 LOG.info("Downloading the artifact {0}".format(log_file))
423 r.download(destination=log_file, target=os.getcwd())
Dennis Dmitriev445b4322018-10-08 14:32:07 +0300424 self.store_server_version(os.path.join(os.getcwd(), 'env_k8s_version'))
Vladimir Jigulina6b018b2018-07-18 15:19:01 +0400425
Vladimir Jigulin4ad52a82018-08-12 05:51:30 +0400426 def combine_xunit(self, path, output):
427 """
428 Function to combine multiple xmls with test results to
429 one.
Vladimir Jigulin34dfa942018-07-23 21:05:48 +0400430
Vladimir Jigulin4ad52a82018-08-12 05:51:30 +0400431 :param path: Path where xmls to combine located
432 :param output: Path to xml file where output will stored
433 :return:
434 """
Victor Ryzhenkine784bbf2018-12-21 03:58:00 +0400435 if self.conformance_node:
436 node = self.conformance_node
437 else:
438 node = self.controller_name
439 LOG.info("Trying to combine xunit at {}".format(node))
Victor Ryzhenkin95046882018-12-29 19:18:40 +0400440 cmd = ("apt-get install python-setuptools -y; "
441 "pip install "
442 "https://github.com/mogaika/xunitmerge/archive/master.zip")
443 LOG.debug('Installing xunitmerge')
444 self._salt.cmd_run(tgt=node, cmd=cmd)
445 LOG.debug('Merging xunit')
446 cmd = ("cd {0}; arg=''; "
447 "for i in $(ls | grep xml); "
448 "do arg=\"$arg $i\"; done && "
449 "xunitmerge $arg {1} || true".format(path, output))
450 self._salt.cmd_run(tgt=node, cmd=cmd)
Vladimir Jigulin34dfa942018-07-23 21:05:48 +0400451
Vladimir Jigulin4ad52a82018-08-12 05:51:30 +0400452 def manage_cncf_archive(self):
453 """
Vladimir Jigulin0c8dd5a2018-08-28 05:08:35 +0400454 Function to untar archive, move files that we are needs to the
455 home folder, prepare it to downloading.
456 Will generate files on controller node:
457 e2e.log, junit_01.xml, cncf_results.tar.gz, version.txt
Vladimir Jigulin4ad52a82018-08-12 05:51:30 +0400458 :return:
459 """
460
Vladimir Jigulin0c8dd5a2018-08-28 05:08:35 +0400461 cmd =\
462 "rm -rf cncf_results.tar.gz result && " \
463 "mkdir result && " \
464 "mv *_sonobuoy_*.tar.gz cncf_results.tar.gz && " \
465 "tar -C result -xzf cncf_results.tar.gz && " \
466 "mv result/plugins/e2e/results/e2e.log . ; " \
467 "mv result/plugins/e2e/results/junit_01.xml . ; " \
468 "kubectl version > version.txt"
Vladimir Jigulin4ad52a82018-08-12 05:51:30 +0400469
Vladimir Jigulin0c8dd5a2018-08-28 05:08:35 +0400470 self.controller_check_call(cmd, raise_on_err=False)
Vladimir Jigulin4ad52a82018-08-12 05:51:30 +0400471
472 @retry(300, exception=DevopsCalledProcessError)
473 def nslookup(self, host, src):
474 """ Run nslookup on controller and return result """
475 return self.controller_check_call("nslookup {0} {1}".format(host, src))
476
477 @retry(300, exception=DevopsCalledProcessError)
Vladimir Jigulin57ecae92018-09-10 22:51:15 +0400478 def curl(self, url, *args):
Vladimir Jigulin4ad52a82018-08-12 05:51:30 +0400479 """
480 Run curl on controller and return stdout
481
482 :param url: url to curl
Vladimir Jigulin57ecae92018-09-10 22:51:15 +0400483 :return: list of strings (with /n at end of every line)
Vladimir Jigulin4ad52a82018-08-12 05:51:30 +0400484 """
Vladimir Jigulin57ecae92018-09-10 22:51:15 +0400485 args = list(args)
486 args.append(url)
487 cmd = "curl -s -S {}".format(
488 " ".join(["'{}'".format(a.replace("'", "\\'")) for a in args]))
489 result = self.controller_check_call(cmd)
490 LOG.debug("{0}\nresult:\n{1}".format(cmd, result['stdout']))
Vladimir Jigulin4ad52a82018-08-12 05:51:30 +0400491 return result['stdout']
Vladimir Jigulin34dfa942018-07-23 21:05:48 +0400492
Dennis Dmitriev445b4322018-10-08 14:32:07 +0300493 def store_server_version(self, env_file_path):
494 """Store Kubernetes server version in bash source file"""
495
496 def digits(string):
497 return ''.join(n for n in string if n.isdigit())
498
499 ver = self.api.api_version.get_code()
500 LOG.debug("Got Kubernetes server version:\n{0}".format(ver))
501
502 env_version = ("export KUBE_SERVER_VERSION={0}.{1}\n"
503 "export KUBE_SERVER_GIT_VERSION={2}\n"
504 .format(digits(ver.major),
505 digits(ver.minor),
506 ver.git_version))
507
508 LOG.info("Kubernetes server version is stored to {0}:\n{1}"
509 .format(env_file_path, env_version))
510 with open(env_file_path, 'w') as kver:
511 kver.write(env_version)
512
Vladimir Jigulina6b018b2018-07-18 15:19:01 +0400513
Vladimir Jigulin4ad52a82018-08-12 05:51:30 +0400514class K8SKubectlCli(object):
515 """ Contain kubectl cli commands and api wrappers"""
516 def __init__(self, manager):
517 self._manager = manager
Vladimir Jigulina6b018b2018-07-18 15:19:01 +0400518
Vladimir Jigulin4ad52a82018-08-12 05:51:30 +0400519 def cli_run(self, namespace, name, image, port, replicas=1):
520 cmd = "kubectl -n {0} run {1} --image={2} --port={3} --replicas={4}".\
521 format(namespace, name, image, port, replicas)
522 return self._manager.controller_check_call(cmd)
Vladimir Jigulina6b018b2018-07-18 15:19:01 +0400523
Vladimir Jigulin4ad52a82018-08-12 05:51:30 +0400524 def run(self, namespace, name, image, port, replicas=1):
525 self.cli_run(namespace, name, image, port, replicas)
526 return self._manager.api.deployments.get(
527 namespace=namespace, name=name)
Vladimir Jigulina6b018b2018-07-18 15:19:01 +0400528
Vladimir Jigulin4ad52a82018-08-12 05:51:30 +0400529 def cli_expose(self, namespace, resource_type, resource_name,
530 service_name=None, port='', service_type='ClusterIP'):
531 cmd = "kubectl -n {0} expose {1} {2} --port={3} --type={4}".format(
532 namespace, resource_type, resource_name, port, service_type)
533 if service_name is not None:
534 cmd += " --name={}".format(service_name)
535 return self._manager.controller_check_call(cmd)
Vladimir Jigulina6b018b2018-07-18 15:19:01 +0400536
Vladimir Jigulin4ad52a82018-08-12 05:51:30 +0400537 def expose(self, resource, service_name=None,
538 port='', service_type='ClusterIP'):
539 self.cli_expose(resource.namespace, resource.resource_type,
540 resource.name, service_name=service_name,
541 port=port, service_type=service_type)
542 return self._manager.api.services.get(
543 namespace=resource.namespace, name=service_name or resource.name)
544
545 def cli_exec(self, namespace, pod_name, cmd, container=''):
546 kubectl_cmd = "kubectl -n {0} exec --container={1} {2} -- {3}".format(
547 namespace, container, pod_name, cmd)
548 return self._manager.controller_check_call(kubectl_cmd)
549
550 # def exec(...), except exec is statement in python
551 def execute(self, pod, cmd, container=''):
552 return self.cli_exec(pod.namespace, pod.name, cmd, container=container)
553
554 def cli_annotate(self, namespace, resource_type, resource_name,
555 annotations, overwrite=False):
556 cmd = "kubectl -n {0} annotate {1} {2} {3}".format(
557 namespace, resource_type, resource_name, annotations)
558 if overwrite:
559 cmd += " --overwrite"
560 return self._manager.controller_check_call(cmd)
561
562 def annotate(self, resource, annotations, overwrite=False):
563 return self.cli_annotate(resource.namespace, resource.resource_type,
564 resource.name, annotations,
565 overwrite=overwrite)
566
567
568class K8SVirtlet(object):
569 """ Contain virtlet-related methods"""
570 def __init__(self, manager, namespace='kube-system'):
571 self._manager = manager
572 self._namespace = namespace
573
574 def get_virtlet_node_pod(self, node_name):
575 for pod in self._manager.api.pods.list(
576 namespace=self._namespace, name_prefix='virtlet-'):
577 if pod.read().spec.node_name == node_name:
578 return pod
579 return None
580
581 def get_pod_dom_uuid(self, pod):
582 uuid_name_map = self.virtlet_execute(
583 pod.read().spec.node_name, 'virsh list --uuid --name')['stdout']
Vladimir Jigulin4ad52a82018-08-12 05:51:30 +0400584 for line in uuid_name_map:
585 if line.rstrip().endswith("-{}".format(pod.name)):
586 return line.split(" ")[0]
587 raise Exception("Cannot detect uuid for pod {}".format(pod.name))
588
589 def virsh_domstats(self, pod):
590 """ get dict of vm stats """
591 uuid = self.get_pod_dom_uuid(pod)
592 result = self.virtlet_execute(
593 pod.read().spec.node_name, 'virsh domstats {}'.format(uuid))
594 stats = dict()
595 for line in result['stdout']:
596 if '=' in line:
597 vals = line.strip().split('=')
598 stats[vals[0]] = vals[1]
599 return stats
600
601 def virtlet_execute(self, node_name, cmd, container='libvirt'):
602 """ execute command inside virtlet container """
603 pod = self.get_virtlet_node_pod(node_name)
604 return self._manager.kubectl.execute(pod, cmd, container)
605
606
607class K8SSampleDeployment(object):
608 """ Wrapper for deployment run=>expose=>check frequent combination """
609 def __init__(self, manager, name,
610 namespace=None,
611 image='gcr.io/google-samples/node-hello:1.0',
612 port=8080,
613 replicas=2):
614 namespace = namespace or manager.api.default_namespace
615
616 self._manager = manager
617 self._port = port
618 self._deployment = \
619 manager.kubectl.run(namespace, name,
620 image=image, port=port, replicas=replicas)
621 self._index = 1 # used to generate svc name
622 self._svc = None # hold last created svc
623
624 def wait_ready(self, timeout=300, interval=5):
625 self._deployment.wait_ready(timeout=timeout, interval=interval)
626 return self
627
628 def svc(self):
629 """ Return the last exposed service"""
630 return self._svc
631
632 def expose(self, service_type='ClusterIP'):
633 service_name = "{0}-s{1}".format(self._deployment.name, self._index)
Vladimir Jigulin90689152018-09-26 15:38:19 +0400634 self._index += 1
Vladimir Jigulin4ad52a82018-08-12 05:51:30 +0400635 self._svc = self._manager.kubectl.expose(
636 self._deployment, port=self._port,
637 service_name=service_name, service_type=service_type)
638 return self._svc
639
640 def curl(self, svc=None, external=False):
641 if svc is None:
642 svc = self.svc()
643 url = "http://{0}:{1}".format(svc.get_ip(external), self._port)
Vladimir Jigulina6b018b2018-07-18 15:19:01 +0400644 if external:
645 return requests.get(url).text
646 else:
Vladimir Jigulin4ad52a82018-08-12 05:51:30 +0400647 return self._manager.curl(url)
Vladimir Jigulina6b018b2018-07-18 15:19:01 +0400648
Vladimir Jigulin4ad52a82018-08-12 05:51:30 +0400649 def is_service_available(self, svc=None, external=False):
650 return "Hello Kubernetes!" in self.curl(svc, external=external)
Vladimir Jigulin90689152018-09-26 15:38:19 +0400651
652 def delete(self):
653 for svc in self._manager.api.services.list_all(
654 name_prefix="{}-s".format(self._deployment.name)):
655 svc.delete()
656 self._deployment.delete()