blob: 3ae1a1bf46fa61eb6a8130c1bd7c0d48e2e89e55 [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 Ryzhenkine784bbf2018-12-21 03:58:00 +0400303 def start_conformance_inside_pod(self, cnf_type='k8s', timeout=60 * 60):
304 """
305 Create conformance pod and wait for results
306 :param cnf_type: k8s or virtlet. choose what conformance you want
307 :param timeout:
308 :return:
309 """
310 if cnf_type == 'k8s':
311 pod_mark = 'conformance'
312 elif cnf_type == 'virtlet':
313 pod_mark = 'virtlet-conformance'
314 else:
315 LOG.error("Unknown conformance type or it even not set")
316 raise RuntimeError("Unknown conformance type")
317 conformance_cmd = "kubectl apply -f /srv/kubernetes/{}.yml" \
318 "".format(pod_mark)
319 self.controller_check_call(conformance_cmd, timeout=900)
320
321 cnf_pod = self.api.pods.get(pod_mark, pod_mark)
322 cnf_pod.wait_running()
323
324 pod = cnf_pod.read()
325 target = "{}.".format(pod.spec.node_name)
326 self.conformance_node = self.__underlay.get_target_node_names(
327 target)[0]
328
329 def cnf_status():
330 pod = cnf_pod.read()
331 status = pod.status.phase
332 LOG.info("Conformance status: {}".format(status))
333 return status
334
335 LOG.info("Waiting for Conformance to complete")
336 helpers.wait(
337 lambda: cnf_status() == ('Succeeded' or 'Failed'),
338 interval=120, timeout=timeout,
339 timeout_msg="Timeout for Conformance reached."
340 )
341
342 pod = cnf_pod.read()
343 status = pod.status.phase
344 if status is 'Failed':
345 describe = "kubectl describe po {0} -n {0}".format(pod_mark)
346 LOG.info(self.controller_check_call(describe, timeout=30))
347 raise RuntimeError("Conformance failed")
348
349 def move_file_to_root_folder(self, filepath):
350 cmd = "mv {0} /root/".format(filepath)
351 if self.conformance_node:
352 self.__underlay.check_call(
353 cmd=cmd, node_name=self.conformance_node,
354 raise_on_err=False)
355 else:
356 LOG.info("Node is not properly set")
357
Vladimir Jigulin4ad52a82018-08-12 05:51:30 +0400358 def extract_file_to_node(self, system='docker',
359 container='virtlet',
360 file_path='report.xml',
361 out_dir='.',
362 **kwargs):
Vladimir Jigulinee1faa52018-06-25 13:00:51 +0400363 """
Vladimir Jigulin4ad52a82018-08-12 05:51:30 +0400364 Download file from docker or k8s container to node
Vladimir Jigulinee1faa52018-06-25 13:00:51 +0400365
Vladimir Jigulin4ad52a82018-08-12 05:51:30 +0400366 :param system: docker or k8s
367 :param container: Full name of part of name
368 :param file_path: File path in container
369 :param kwargs: Used to control pod and namespace
370 :param out_dir: Output directory
371 :return:
Vladimir Jigulinee1faa52018-06-25 13:00:51 +0400372 """
Vladimir Jigulin0c8dd5a2018-08-28 05:08:35 +0400373 with self.__underlay.remote(node_name=self.controller_name) as remote:
Vladimir Jigulin4ad52a82018-08-12 05:51:30 +0400374 if system is 'docker':
375 cmd = ("docker ps --all | grep \"{0}\" |"
376 " awk '{{print $1}}'".format(container))
377 result = remote.check_call(cmd, raise_on_err=False)
378 if result['stdout']:
379 container_id = result['stdout'][0].strip()
380 else:
381 LOG.info('No container found, skipping extraction...')
382 return
383 cmd = "docker start {}".format(container_id)
384 remote.check_call(cmd, raise_on_err=False)
385 cmd = "docker cp \"{0}:/{1}\" \"{2}\"".format(
386 container_id, file_path, out_dir)
387 remote.check_call(cmd, raise_on_err=False)
388 else:
389 # system is k8s
390 pod_name = kwargs.get('pod_name')
391 pod_namespace = kwargs.get('pod_namespace')
392 cmd = 'kubectl cp \"{0}/{1}:/{2}\" \"{3}\"'.format(
393 pod_namespace, pod_name, file_path, out_dir)
394 remote.check_call(cmd, raise_on_err=False)
Vladimir Jigulina6b018b2018-07-18 15:19:01 +0400395
Vladimir Jigulin4ad52a82018-08-12 05:51:30 +0400396 def download_k8s_logs(self, files):
397 """
398 Download JUnit report and conformance logs from cluster
399 :param files:
400 :return:
401 """
Victor Ryzhenkine784bbf2018-12-21 03:58:00 +0400402 if self.conformance_node:
403 node = self.conformance_node
404 else:
405 node = self.controller_name
406 LOG.info("Trying to get logs at {}".format(node))
Vladimir Jigulin4ad52a82018-08-12 05:51:30 +0400407 master_host = self.__config.salt.salt_master_host
408 with self.__underlay.remote(host=master_host) as r:
409 for log_file in files:
410 cmd = "rsync -r \"{0}:/root/{1}\" /root/".format(
Victor Ryzhenkine784bbf2018-12-21 03:58:00 +0400411 node, log_file)
Vladimir Jigulin4ad52a82018-08-12 05:51:30 +0400412 r.check_call(cmd, raise_on_err=False)
413 LOG.info("Downloading the artifact {0}".format(log_file))
414 r.download(destination=log_file, target=os.getcwd())
Dennis Dmitriev445b4322018-10-08 14:32:07 +0300415 self.store_server_version(os.path.join(os.getcwd(), 'env_k8s_version'))
Vladimir Jigulina6b018b2018-07-18 15:19:01 +0400416
Vladimir Jigulin4ad52a82018-08-12 05:51:30 +0400417 def combine_xunit(self, path, output):
418 """
419 Function to combine multiple xmls with test results to
420 one.
Vladimir Jigulin34dfa942018-07-23 21:05:48 +0400421
Vladimir Jigulin4ad52a82018-08-12 05:51:30 +0400422 :param path: Path where xmls to combine located
423 :param output: Path to xml file where output will stored
424 :return:
425 """
Victor Ryzhenkine784bbf2018-12-21 03:58:00 +0400426 if self.conformance_node:
427 node = self.conformance_node
428 else:
429 node = self.controller_name
430 LOG.info("Trying to combine xunit at {}".format(node))
431 with self.__underlay.remote(node_name=node) as r:
Vladimir Jigulin4ad52a82018-08-12 05:51:30 +0400432 cmd = ("apt-get install python-setuptools -y; "
433 "pip install "
434 "https://github.com/mogaika/xunitmerge/archive/master.zip")
435 LOG.debug('Installing xunitmerge')
436 r.check_call(cmd, raise_on_err=False)
437 LOG.debug('Merging xunit')
Dennis Dmitrievee5ef232018-08-31 13:53:18 +0300438 cmd = ("cd {0}; arg=''; "
Vladimir Jigulin4ad52a82018-08-12 05:51:30 +0400439 "for i in $(ls | grep xml); "
440 "do arg=\"$arg $i\"; done && "
441 "xunitmerge $arg {1}".format(path, output))
442 r.check_call(cmd, raise_on_err=False)
Vladimir Jigulin34dfa942018-07-23 21:05:48 +0400443
Vladimir Jigulin4ad52a82018-08-12 05:51:30 +0400444 def manage_cncf_archive(self):
445 """
Vladimir Jigulin0c8dd5a2018-08-28 05:08:35 +0400446 Function to untar archive, move files that we are needs to the
447 home folder, prepare it to downloading.
448 Will generate files on controller node:
449 e2e.log, junit_01.xml, cncf_results.tar.gz, version.txt
Vladimir Jigulin4ad52a82018-08-12 05:51:30 +0400450 :return:
451 """
452
Vladimir Jigulin0c8dd5a2018-08-28 05:08:35 +0400453 cmd =\
454 "rm -rf cncf_results.tar.gz result && " \
455 "mkdir result && " \
456 "mv *_sonobuoy_*.tar.gz cncf_results.tar.gz && " \
457 "tar -C result -xzf cncf_results.tar.gz && " \
458 "mv result/plugins/e2e/results/e2e.log . ; " \
459 "mv result/plugins/e2e/results/junit_01.xml . ; " \
460 "kubectl version > version.txt"
Vladimir Jigulin4ad52a82018-08-12 05:51:30 +0400461
Vladimir Jigulin0c8dd5a2018-08-28 05:08:35 +0400462 self.controller_check_call(cmd, raise_on_err=False)
Vladimir Jigulin4ad52a82018-08-12 05:51:30 +0400463
464 @retry(300, exception=DevopsCalledProcessError)
465 def nslookup(self, host, src):
466 """ Run nslookup on controller and return result """
467 return self.controller_check_call("nslookup {0} {1}".format(host, src))
468
469 @retry(300, exception=DevopsCalledProcessError)
Vladimir Jigulin57ecae92018-09-10 22:51:15 +0400470 def curl(self, url, *args):
Vladimir Jigulin4ad52a82018-08-12 05:51:30 +0400471 """
472 Run curl on controller and return stdout
473
474 :param url: url to curl
Vladimir Jigulin57ecae92018-09-10 22:51:15 +0400475 :return: list of strings (with /n at end of every line)
Vladimir Jigulin4ad52a82018-08-12 05:51:30 +0400476 """
Vladimir Jigulin57ecae92018-09-10 22:51:15 +0400477 args = list(args)
478 args.append(url)
479 cmd = "curl -s -S {}".format(
480 " ".join(["'{}'".format(a.replace("'", "\\'")) for a in args]))
481 result = self.controller_check_call(cmd)
482 LOG.debug("{0}\nresult:\n{1}".format(cmd, result['stdout']))
Vladimir Jigulin4ad52a82018-08-12 05:51:30 +0400483 return result['stdout']
Vladimir Jigulin34dfa942018-07-23 21:05:48 +0400484
Dennis Dmitriev445b4322018-10-08 14:32:07 +0300485 def store_server_version(self, env_file_path):
486 """Store Kubernetes server version in bash source file"""
487
488 def digits(string):
489 return ''.join(n for n in string if n.isdigit())
490
491 ver = self.api.api_version.get_code()
492 LOG.debug("Got Kubernetes server version:\n{0}".format(ver))
493
494 env_version = ("export KUBE_SERVER_VERSION={0}.{1}\n"
495 "export KUBE_SERVER_GIT_VERSION={2}\n"
496 .format(digits(ver.major),
497 digits(ver.minor),
498 ver.git_version))
499
500 LOG.info("Kubernetes server version is stored to {0}:\n{1}"
501 .format(env_file_path, env_version))
502 with open(env_file_path, 'w') as kver:
503 kver.write(env_version)
504
Vladimir Jigulina6b018b2018-07-18 15:19:01 +0400505
Vladimir Jigulin4ad52a82018-08-12 05:51:30 +0400506class K8SKubectlCli(object):
507 """ Contain kubectl cli commands and api wrappers"""
508 def __init__(self, manager):
509 self._manager = manager
Vladimir Jigulina6b018b2018-07-18 15:19:01 +0400510
Vladimir Jigulin4ad52a82018-08-12 05:51:30 +0400511 def cli_run(self, namespace, name, image, port, replicas=1):
512 cmd = "kubectl -n {0} run {1} --image={2} --port={3} --replicas={4}".\
513 format(namespace, name, image, port, replicas)
514 return self._manager.controller_check_call(cmd)
Vladimir Jigulina6b018b2018-07-18 15:19:01 +0400515
Vladimir Jigulin4ad52a82018-08-12 05:51:30 +0400516 def run(self, namespace, name, image, port, replicas=1):
517 self.cli_run(namespace, name, image, port, replicas)
518 return self._manager.api.deployments.get(
519 namespace=namespace, name=name)
Vladimir Jigulina6b018b2018-07-18 15:19:01 +0400520
Vladimir Jigulin4ad52a82018-08-12 05:51:30 +0400521 def cli_expose(self, namespace, resource_type, resource_name,
522 service_name=None, port='', service_type='ClusterIP'):
523 cmd = "kubectl -n {0} expose {1} {2} --port={3} --type={4}".format(
524 namespace, resource_type, resource_name, port, service_type)
525 if service_name is not None:
526 cmd += " --name={}".format(service_name)
527 return self._manager.controller_check_call(cmd)
Vladimir Jigulina6b018b2018-07-18 15:19:01 +0400528
Vladimir Jigulin4ad52a82018-08-12 05:51:30 +0400529 def expose(self, resource, service_name=None,
530 port='', service_type='ClusterIP'):
531 self.cli_expose(resource.namespace, resource.resource_type,
532 resource.name, service_name=service_name,
533 port=port, service_type=service_type)
534 return self._manager.api.services.get(
535 namespace=resource.namespace, name=service_name or resource.name)
536
537 def cli_exec(self, namespace, pod_name, cmd, container=''):
538 kubectl_cmd = "kubectl -n {0} exec --container={1} {2} -- {3}".format(
539 namespace, container, pod_name, cmd)
540 return self._manager.controller_check_call(kubectl_cmd)
541
542 # def exec(...), except exec is statement in python
543 def execute(self, pod, cmd, container=''):
544 return self.cli_exec(pod.namespace, pod.name, cmd, container=container)
545
546 def cli_annotate(self, namespace, resource_type, resource_name,
547 annotations, overwrite=False):
548 cmd = "kubectl -n {0} annotate {1} {2} {3}".format(
549 namespace, resource_type, resource_name, annotations)
550 if overwrite:
551 cmd += " --overwrite"
552 return self._manager.controller_check_call(cmd)
553
554 def annotate(self, resource, annotations, overwrite=False):
555 return self.cli_annotate(resource.namespace, resource.resource_type,
556 resource.name, annotations,
557 overwrite=overwrite)
558
559
560class K8SVirtlet(object):
561 """ Contain virtlet-related methods"""
562 def __init__(self, manager, namespace='kube-system'):
563 self._manager = manager
564 self._namespace = namespace
565
566 def get_virtlet_node_pod(self, node_name):
567 for pod in self._manager.api.pods.list(
568 namespace=self._namespace, name_prefix='virtlet-'):
569 if pod.read().spec.node_name == node_name:
570 return pod
571 return None
572
573 def get_pod_dom_uuid(self, pod):
574 uuid_name_map = self.virtlet_execute(
575 pod.read().spec.node_name, 'virsh list --uuid --name')['stdout']
Vladimir Jigulin4ad52a82018-08-12 05:51:30 +0400576 for line in uuid_name_map:
577 if line.rstrip().endswith("-{}".format(pod.name)):
578 return line.split(" ")[0]
579 raise Exception("Cannot detect uuid for pod {}".format(pod.name))
580
581 def virsh_domstats(self, pod):
582 """ get dict of vm stats """
583 uuid = self.get_pod_dom_uuid(pod)
584 result = self.virtlet_execute(
585 pod.read().spec.node_name, 'virsh domstats {}'.format(uuid))
586 stats = dict()
587 for line in result['stdout']:
588 if '=' in line:
589 vals = line.strip().split('=')
590 stats[vals[0]] = vals[1]
591 return stats
592
593 def virtlet_execute(self, node_name, cmd, container='libvirt'):
594 """ execute command inside virtlet container """
595 pod = self.get_virtlet_node_pod(node_name)
596 return self._manager.kubectl.execute(pod, cmd, container)
597
598
599class K8SSampleDeployment(object):
600 """ Wrapper for deployment run=>expose=>check frequent combination """
601 def __init__(self, manager, name,
602 namespace=None,
603 image='gcr.io/google-samples/node-hello:1.0',
604 port=8080,
605 replicas=2):
606 namespace = namespace or manager.api.default_namespace
607
608 self._manager = manager
609 self._port = port
610 self._deployment = \
611 manager.kubectl.run(namespace, name,
612 image=image, port=port, replicas=replicas)
613 self._index = 1 # used to generate svc name
614 self._svc = None # hold last created svc
615
616 def wait_ready(self, timeout=300, interval=5):
617 self._deployment.wait_ready(timeout=timeout, interval=interval)
618 return self
619
620 def svc(self):
621 """ Return the last exposed service"""
622 return self._svc
623
624 def expose(self, service_type='ClusterIP'):
625 service_name = "{0}-s{1}".format(self._deployment.name, self._index)
Vladimir Jigulin90689152018-09-26 15:38:19 +0400626 self._index += 1
Vladimir Jigulin4ad52a82018-08-12 05:51:30 +0400627 self._svc = self._manager.kubectl.expose(
628 self._deployment, port=self._port,
629 service_name=service_name, service_type=service_type)
630 return self._svc
631
632 def curl(self, svc=None, external=False):
633 if svc is None:
634 svc = self.svc()
635 url = "http://{0}:{1}".format(svc.get_ip(external), self._port)
Vladimir Jigulina6b018b2018-07-18 15:19:01 +0400636 if external:
637 return requests.get(url).text
638 else:
Vladimir Jigulin4ad52a82018-08-12 05:51:30 +0400639 return self._manager.curl(url)
Vladimir Jigulina6b018b2018-07-18 15:19:01 +0400640
Vladimir Jigulin4ad52a82018-08-12 05:51:30 +0400641 def is_service_available(self, svc=None, external=False):
642 return "Hello Kubernetes!" in self.curl(svc, external=external)
Vladimir Jigulin90689152018-09-26 15:38:19 +0400643
644 def delete(self):
645 for svc in self._manager.api.services.list_all(
646 name_prefix="{}-s".format(self._deployment.name)):
647 svc.delete()
648 self._deployment.delete()