blob: 5e8ea56c93b2025096b0de7c0c81ae2502703175 [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)
44 super(K8SManager, self).__init__(config=config, underlay=underlay)
Artem Panchenko0594cd72017-06-12 13:25:26 +030045
46 def install(self, commands):
47 self.execute_commands(commands,
48 label='Install Kubernetes services')
49 self.__config.k8s.k8s_installed = True
50 self.__config.k8s.kube_host = self.get_proxy_api()
51
52 def get_proxy_api(self):
53 k8s_proxy_ip_pillars = self._salt.get_pillar(
vrovachev99228d32017-06-08 19:46:10 +040054 tgt='I@haproxy:proxy:enabled:true and I@kubernetes:master',
Artem Panchenko0594cd72017-06-12 13:25:26 +030055 pillar='haproxy:proxy:listen:k8s_secure:binds:address')
vrovachev99228d32017-06-08 19:46:10 +040056 k8s_hosts = self._salt.get_pillar(
57 tgt='I@haproxy:proxy:enabled:true and I@kubernetes:master',
58 pillar='kubernetes:pool:apiserver:host')
Artem Panchenko0594cd72017-06-12 13:25:26 +030059 k8s_proxy_ip = set([ip
60 for item in k8s_proxy_ip_pillars
Dina Belovae6fdffb2017-09-19 13:58:34 -070061 for node, ip in item.items() if ip])
vrovachev99228d32017-06-08 19:46:10 +040062 k8s_hosts = set([ip
Dina Belovae6fdffb2017-09-19 13:58:34 -070063 for item in k8s_hosts
64 for node, ip in item.items() if ip])
vrovachev99228d32017-06-08 19:46:10 +040065 assert len(k8s_hosts) == 1, (
66 "Found more than one Kubernetes API hosts in pillars:{0}, "
67 "expected one!").format(k8s_hosts)
68 k8s_host = k8s_hosts.pop()
69 assert k8s_host in k8s_proxy_ip, (
70 "Kubernetes API host:{0} not found in proxies:{} "
71 "on k8s master nodes. K8s proxies are expected on "
72 "nodes with K8s master").format(k8s_host, k8s_proxy_ip)
73 return k8s_host
Artem Panchenko0594cd72017-06-12 13:25:26 +030074
Vladimir Jigulin4ad52a82018-08-12 05:51:30 +040075 def _api_init(self):
76 ca_result = self.controller_check_call(
77 'base64 --wrap=0 /etc/kubernetes/ssl/ca-kubernetes.crt')
78
79 self._api = cluster.K8sCluster(
80 user=self.__config.k8s_deploy.kubernetes_admin_user,
81 password=self.__config.k8s_deploy.kubernetes_admin_password,
82 ca=ca_result['stdout'][0],
83 host=self.__config.k8s.kube_host,
84 port=self.__config.k8s.kube_apiserver_port)
85
Artem Panchenko0594cd72017-06-12 13:25:26 +030086 @property
87 def api(self):
Vladimir Jigulin4ad52a82018-08-12 05:51:30 +040088 """
89 :rtype: cluster.K8sCluster
90 """
91 if self._api is None:
92 self._api_init()
93 return self._api
Artem Panchenko0594cd72017-06-12 13:25:26 +030094
Vladimir Jigulin4ad52a82018-08-12 05:51:30 +040095 def get_controllers(self):
96 """ Return list of controllers ssh underlays """
Vladimir Jigulin34dfa942018-07-23 21:05:48 +040097 return [node for node in self.__config.underlay.ssh if
98 ext.UNDERLAY_NODE_ROLES.k8s_controller in node['roles']]
99
Vladimir Jigulin4ad52a82018-08-12 05:51:30 +0400100 def get_masters(self):
101 """ Return list of kubernetes masters hosts fqdn """
102 masters_fqdn = self._salt.get_pillar(
103 tgt='I@kubernetes:master', pillar='linux:network:fqdn')
104 return [self.__underlay.host_by_node_name(node_name=v)
105 for pillar in masters_fqdn for k, v in pillar.items()]
106
Victor Ryzhenkin66d39372017-09-28 19:25:48 +0400107 @property
Vladimir Jigulin4ad52a82018-08-12 05:51:30 +0400108 def controller_name(self):
109 """ Return node name of controller node that used for all actions """
110 names = [node['node_name'] for node in self.get_controllers()]
111 # we want to return same controller name every time
112 names.sort()
113 return names[0]
Victor Ryzhenkin66d39372017-09-28 19:25:48 +0400114
Dennis Dmitriev66650fc2018-11-02 11:04:37 +0200115 @property
116 def controller_minion_id(self):
117 """ Return node name of controller node that used for all actions """
118 minion_ids = [minion_id['minion_id'] for minion_id in
119 self.get_controllers()]
120 # we want to return same controller name every time
121 minion_ids.sort()
122 return minion_ids[0]
123
124 @property
125 def is_metallb_enabled(self):
126 ctl_tgt = self.controller_minion_id
127 LOG.debug("Controller target: {}".format(ctl_tgt))
128
129 result = self._salt.get_pillar(
130 tgt=ctl_tgt,
131 pillar='kubernetes:common:addons:metallb:enabled')
132 metallb = result[0].get(ctl_tgt, False)
133 LOG.info("{} kubernetes:common:addons:metallb:enabled: {}"
134 .format(ctl_tgt, bool(metallb)))
135 return metallb
136
137 @property
138 def is_ingress_nginx_enabled(self):
139 ctl_tgt = self.controller_minion_id
140 LOG.debug("Controller target: {}".format(ctl_tgt))
141
142 result = self._salt.get_pillar(
143 tgt=ctl_tgt,
144 pillar='kubernetes:common:addons:ingress-nginx:enabled')
145 ingress_nginx = result[0].get(ctl_tgt, False)
146 LOG.info("{} kubernetes:common:addons:ingress-nginx:enabled: {}"
147 .format(ctl_tgt, bool(ingress_nginx)))
148 return ingress_nginx
149
Vladimir Jigulin4ad52a82018-08-12 05:51:30 +0400150 def controller_check_call(self, cmd, **kwargs):
151 """ Run command on controller and return result """
152 LOG.info("running cmd on k8s controller: {}".format(cmd))
Vladimir Jigulinee1faa52018-06-25 13:00:51 +0400153 return self.__underlay.check_call(
Vladimir Jigulin4ad52a82018-08-12 05:51:30 +0400154 cmd=cmd, node_name=self.controller_name, **kwargs)
Artem Panchenko501e67e2017-06-14 14:59:18 +0300155
Vladimir Jigulin4ad52a82018-08-12 05:51:30 +0400156 def get_keepalived_vip(self):
Vladimir Jigulin62bcf462018-05-28 18:17:01 +0400157 """
Vladimir Jigulin4ad52a82018-08-12 05:51:30 +0400158 Return k8s VIP IP address
Vladimir Jigulin62bcf462018-05-28 18:17:01 +0400159
Vladimir Jigulin4ad52a82018-08-12 05:51:30 +0400160 :return: str, IP address
Vladimir Jigulin62bcf462018-05-28 18:17:01 +0400161 """
Vladimir Jigulin4ad52a82018-08-12 05:51:30 +0400162 ctl_vip_pillar = self._salt.get_pillar(
163 tgt="I@kubernetes:control:enabled:True",
164 pillar="_param:cluster_vip_address")[0]
165 return ctl_vip_pillar.values()[0]
Vladimir Jigulin62bcf462018-05-28 18:17:01 +0400166
Vladimir Jigulin4ad52a82018-08-12 05:51:30 +0400167 def run_sample_deployment(self, name, **kwargs):
168 return K8SSampleDeployment(self, name, **kwargs)
Victor Ryzhenkin3ffa2b42017-10-05 16:38:44 +0400169
Vladimir Jigulin4ad52a82018-08-12 05:51:30 +0400170 def get_pod_ips_from_container(self, pod_name, exclude_local=True,
171 namespace='default'):
172 """ Get ips from container using 'ip a'
173 Required for cni-genie multi-cni cases
174
175 :return: list of IP adresses
Victor Ryzhenkin3ffa2b42017-10-05 16:38:44 +0400176 """
Vladimir Jigulin4ad52a82018-08-12 05:51:30 +0400177 cmd = "ip a|grep \"inet \"|awk '{{print $2}}'"
178 result = self.kubectl.cli_exec(namespace, pod_name, cmd)['stdout']
179 ips = [line.strip().split('/')[0] for line in result]
180 if exclude_local:
181 ips = [ip for ip in ips if not ip.startswith("127.")]
182 return ips
Victor Ryzhenkin3ffa2b42017-10-05 16:38:44 +0400183
Vladimir Jigulin4ad52a82018-08-12 05:51:30 +0400184 def update_k8s_version(self, tag):
Vladimir Jigulin62bcf462018-05-28 18:17:01 +0400185 """
186 Update k8s images tag version in cluster meta and apply required
187 for update states
188
189 :param tag: New version tag of k8s images
190 :return:
191 """
192 master_host = self.__config.salt.salt_master_host
193
194 def update_image_tag_meta(config, image_name):
195 image_old = config.get(image_name)
196 image_base = image_old.split(':')[0]
197 image_new = "{}:{}".format(image_base, tag)
198 LOG.info("Changing k8s '{0}' image cluster meta to '{1}'".format(
199 image_name, image_new))
200
201 with self.__underlay.remote(host=master_host) as r:
202 cmd = "salt-call reclass.cluster_meta_set" \
203 " name={0} value={1}".format(image_name, image_new)
204 r.check_call(cmd)
205 return image_new
206
207 cfg = self.__config
208
209 update_image_tag_meta(cfg.k8s_deploy, "kubernetes_hyperkube_image")
210 update_image_tag_meta(cfg.k8s_deploy, "kubernetes_pause_image")
211 cfg.k8s.k8s_conformance_image = update_image_tag_meta(
212 cfg.k8s, "k8s_conformance_image")
213
214 steps_path = cfg.k8s_deploy.k8s_update_steps_path
215 update_commands = self.__underlay.read_template(steps_path)
216 self.execute_commands(
217 update_commands, label="Updating kubernetes to '{}'".format(tag))
Vladimir Jigulinee1faa52018-06-25 13:00:51 +0400218
Vladimir Jigulin4ad52a82018-08-12 05:51:30 +0400219 def run_conformance(self, timeout=60*60, log_out='k8s_conformance.log',
220 raise_on_err=True, node_name=None,
221 api_server='http://127.0.0.1:8080'):
222 if node_name is None:
223 node_name = self.controller_name
224 cmd = "set -o pipefail; docker run --net=host " \
225 "-e API_SERVER='{api}' {image} | tee '{log}'".format(
226 api=api_server, log=log_out,
227 image=self.__config.k8s.k8s_conformance_image)
228 return self.__underlay.check_call(
229 cmd=cmd, node_name=node_name, timeout=timeout,
Dennis Dmitrieveb50ce12018-09-27 13:34:32 +0300230 raise_on_err=raise_on_err, verbose=True)
Vladimir Jigulin4ad52a82018-08-12 05:51:30 +0400231
232 def run_virtlet_conformance(self, timeout=60 * 120,
Dennis Dmitriev34fd3002018-11-15 18:25:16 +0200233 log_file='virtlet_conformance.log',
234 report_name="report.xml"):
Vladimir Jigulin4ad52a82018-08-12 05:51:30 +0400235 if self.__config.k8s.run_extended_virtlet_conformance:
236 ci_image = "cloud-images.ubuntu.com/xenial/current/" \
237 "xenial-server-cloudimg-amd64-disk1.img"
238 cmd = ("set -o pipefail; "
239 "docker run --net=host {0} /virtlet-e2e-tests "
Dennis Dmitriev34fd3002018-11-15 18:25:16 +0200240 "-include-cloud-init-tests -junitOutput {3} "
Vladimir Jigulin4ad52a82018-08-12 05:51:30 +0400241 "-image {2} -sshuser ubuntu -memoryLimit 1024 "
242 "-alsologtostderr -cluster-url http://127.0.0.1:8080 "
243 "-ginkgo.focus '\[Conformance\]' "
244 "| tee {1}".format(
245 self.__config.k8s_deploy.kubernetes_virtlet_image,
Dennis Dmitriev34fd3002018-11-15 18:25:16 +0200246 log_file, ci_image, report_name))
Vladimir Jigulin4ad52a82018-08-12 05:51:30 +0400247 else:
248 cmd = ("set -o pipefail; "
249 "docker run --net=host {0} /virtlet-e2e-tests "
Dennis Dmitriev34fd3002018-11-15 18:25:16 +0200250 "-junitOutput {2} "
Vladimir Jigulin4ad52a82018-08-12 05:51:30 +0400251 "-alsologtostderr -cluster-url http://127.0.0.1:8080 "
252 "-ginkgo.focus '\[Conformance\]' "
253 "| tee {1}".format(
254 self.__config.k8s_deploy.kubernetes_virtlet_image,
Dennis Dmitriev34fd3002018-11-15 18:25:16 +0200255 log_file, report_name))
Vladimir Jigulin4ad52a82018-08-12 05:51:30 +0400256 LOG.info("Executing: {}".format(cmd))
257 with self.__underlay.remote(
258 node_name=self.controller_name) as remote:
259 result = remote.check_call(cmd, timeout=timeout)
260 stderr = result['stderr']
261 stdout = result['stdout']
262 LOG.info("Test results stdout: {}".format(stdout))
263 LOG.info("Test results stderr: {}".format(stderr))
264 return result
265
266 def start_k8s_cncf_verification(self, timeout=60 * 90):
Vladimir Jigulin0c8dd5a2018-08-28 05:08:35 +0400267 """
268 Build sonobuoy using golang docker image and install it in system
269 Then generate sonobuoy verification manifest using gen command
270 and wait for it to end using status annotation
271 TODO (vjigulin): All sonobuoy methods can be improved if we define
272 correct KUBECONFIG env variable
273 """
274 cncf_cmd =\
275 'docker run --network host --name sonobuoy golang:1.11 bash -c ' \
276 '"go get -d -v github.com/heptio/sonobuoy && ' \
277 'go install -v github.com/heptio/sonobuoy" && ' \
278 'docker cp sonobuoy:/go/bin/sonobuoy /usr/local/bin/ && ' \
279 'docker rm sonobuoy && ' \
280 'sonobuoy gen | kubectl apply -f -'
Vladimir Jigulin4ad52a82018-08-12 05:51:30 +0400281
Vladimir Jigulin0c8dd5a2018-08-28 05:08:35 +0400282 self.controller_check_call(cncf_cmd, timeout=900)
283
284 sonobuoy_pod = self.api.pods.get('sonobuoy', 'heptio-sonobuoy')
285 sonobuoy_pod.wait_running()
286
287 def sonobuoy_status():
288 annotations = sonobuoy_pod.read().metadata.annotations
289 json_status = annotations['sonobuoy.hept.io/status']
290 status = yaml.safe_load(json_status)['status']
291 if status != 'running':
292 LOG.info("CNCF status: {}".format(json_status))
293 return yaml.safe_load(json_status)['status']
294
295 LOG.info("Waiting for CNCF to complete")
296 helpers.wait(
297 lambda: sonobuoy_status() == 'complete',
298 interval=30, timeout=timeout,
299 timeout_msg="Timeout for CNCF reached."
300 )
Vladimir Jigulin4ad52a82018-08-12 05:51:30 +0400301
302 def extract_file_to_node(self, system='docker',
303 container='virtlet',
304 file_path='report.xml',
305 out_dir='.',
306 **kwargs):
Vladimir Jigulinee1faa52018-06-25 13:00:51 +0400307 """
Vladimir Jigulin4ad52a82018-08-12 05:51:30 +0400308 Download file from docker or k8s container to node
Vladimir Jigulinee1faa52018-06-25 13:00:51 +0400309
Vladimir Jigulin4ad52a82018-08-12 05:51:30 +0400310 :param system: docker or k8s
311 :param container: Full name of part of name
312 :param file_path: File path in container
313 :param kwargs: Used to control pod and namespace
314 :param out_dir: Output directory
315 :return:
Vladimir Jigulinee1faa52018-06-25 13:00:51 +0400316 """
Vladimir Jigulin0c8dd5a2018-08-28 05:08:35 +0400317 with self.__underlay.remote(node_name=self.controller_name) as remote:
Vladimir Jigulin4ad52a82018-08-12 05:51:30 +0400318 if system is 'docker':
319 cmd = ("docker ps --all | grep \"{0}\" |"
320 " awk '{{print $1}}'".format(container))
321 result = remote.check_call(cmd, raise_on_err=False)
322 if result['stdout']:
323 container_id = result['stdout'][0].strip()
324 else:
325 LOG.info('No container found, skipping extraction...')
326 return
327 cmd = "docker start {}".format(container_id)
328 remote.check_call(cmd, raise_on_err=False)
329 cmd = "docker cp \"{0}:/{1}\" \"{2}\"".format(
330 container_id, file_path, out_dir)
331 remote.check_call(cmd, raise_on_err=False)
332 else:
333 # system is k8s
334 pod_name = kwargs.get('pod_name')
335 pod_namespace = kwargs.get('pod_namespace')
336 cmd = 'kubectl cp \"{0}/{1}:/{2}\" \"{3}\"'.format(
337 pod_namespace, pod_name, file_path, out_dir)
338 remote.check_call(cmd, raise_on_err=False)
Vladimir Jigulina6b018b2018-07-18 15:19:01 +0400339
Vladimir Jigulin4ad52a82018-08-12 05:51:30 +0400340 def download_k8s_logs(self, files):
341 """
342 Download JUnit report and conformance logs from cluster
343 :param files:
344 :return:
345 """
346 master_host = self.__config.salt.salt_master_host
347 with self.__underlay.remote(host=master_host) as r:
348 for log_file in files:
349 cmd = "rsync -r \"{0}:/root/{1}\" /root/".format(
350 self.controller_name, log_file)
351 r.check_call(cmd, raise_on_err=False)
352 LOG.info("Downloading the artifact {0}".format(log_file))
353 r.download(destination=log_file, target=os.getcwd())
Dennis Dmitriev445b4322018-10-08 14:32:07 +0300354 self.store_server_version(os.path.join(os.getcwd(), 'env_k8s_version'))
Vladimir Jigulina6b018b2018-07-18 15:19:01 +0400355
Vladimir Jigulin4ad52a82018-08-12 05:51:30 +0400356 def combine_xunit(self, path, output):
357 """
358 Function to combine multiple xmls with test results to
359 one.
Vladimir Jigulin34dfa942018-07-23 21:05:48 +0400360
Vladimir Jigulin4ad52a82018-08-12 05:51:30 +0400361 :param path: Path where xmls to combine located
362 :param output: Path to xml file where output will stored
363 :return:
364 """
365 with self.__underlay.remote(node_name=self.controller_name) as r:
366 cmd = ("apt-get install python-setuptools -y; "
367 "pip install "
368 "https://github.com/mogaika/xunitmerge/archive/master.zip")
369 LOG.debug('Installing xunitmerge')
370 r.check_call(cmd, raise_on_err=False)
371 LOG.debug('Merging xunit')
Dennis Dmitrievee5ef232018-08-31 13:53:18 +0300372 cmd = ("cd {0}; arg=''; "
Vladimir Jigulin4ad52a82018-08-12 05:51:30 +0400373 "for i in $(ls | grep xml); "
374 "do arg=\"$arg $i\"; done && "
375 "xunitmerge $arg {1}".format(path, output))
376 r.check_call(cmd, raise_on_err=False)
Vladimir Jigulin34dfa942018-07-23 21:05:48 +0400377
Vladimir Jigulin4ad52a82018-08-12 05:51:30 +0400378 def manage_cncf_archive(self):
379 """
Vladimir Jigulin0c8dd5a2018-08-28 05:08:35 +0400380 Function to untar archive, move files that we are needs to the
381 home folder, prepare it to downloading.
382 Will generate files on controller node:
383 e2e.log, junit_01.xml, cncf_results.tar.gz, version.txt
Vladimir Jigulin4ad52a82018-08-12 05:51:30 +0400384 :return:
385 """
386
Vladimir Jigulin0c8dd5a2018-08-28 05:08:35 +0400387 cmd =\
388 "rm -rf cncf_results.tar.gz result && " \
389 "mkdir result && " \
390 "mv *_sonobuoy_*.tar.gz cncf_results.tar.gz && " \
391 "tar -C result -xzf cncf_results.tar.gz && " \
392 "mv result/plugins/e2e/results/e2e.log . ; " \
393 "mv result/plugins/e2e/results/junit_01.xml . ; " \
394 "kubectl version > version.txt"
Vladimir Jigulin4ad52a82018-08-12 05:51:30 +0400395
Vladimir Jigulin0c8dd5a2018-08-28 05:08:35 +0400396 self.controller_check_call(cmd, raise_on_err=False)
Vladimir Jigulin4ad52a82018-08-12 05:51:30 +0400397
398 @retry(300, exception=DevopsCalledProcessError)
399 def nslookup(self, host, src):
400 """ Run nslookup on controller and return result """
401 return self.controller_check_call("nslookup {0} {1}".format(host, src))
402
403 @retry(300, exception=DevopsCalledProcessError)
Vladimir Jigulin57ecae92018-09-10 22:51:15 +0400404 def curl(self, url, *args):
Vladimir Jigulin4ad52a82018-08-12 05:51:30 +0400405 """
406 Run curl on controller and return stdout
407
408 :param url: url to curl
Vladimir Jigulin57ecae92018-09-10 22:51:15 +0400409 :return: list of strings (with /n at end of every line)
Vladimir Jigulin4ad52a82018-08-12 05:51:30 +0400410 """
Vladimir Jigulin57ecae92018-09-10 22:51:15 +0400411 args = list(args)
412 args.append(url)
413 cmd = "curl -s -S {}".format(
414 " ".join(["'{}'".format(a.replace("'", "\\'")) for a in args]))
415 result = self.controller_check_call(cmd)
416 LOG.debug("{0}\nresult:\n{1}".format(cmd, result['stdout']))
Vladimir Jigulin4ad52a82018-08-12 05:51:30 +0400417 return result['stdout']
Vladimir Jigulin34dfa942018-07-23 21:05:48 +0400418
Dennis Dmitriev445b4322018-10-08 14:32:07 +0300419 def store_server_version(self, env_file_path):
420 """Store Kubernetes server version in bash source file"""
421
422 def digits(string):
423 return ''.join(n for n in string if n.isdigit())
424
425 ver = self.api.api_version.get_code()
426 LOG.debug("Got Kubernetes server version:\n{0}".format(ver))
427
428 env_version = ("export KUBE_SERVER_VERSION={0}.{1}\n"
429 "export KUBE_SERVER_GIT_VERSION={2}\n"
430 .format(digits(ver.major),
431 digits(ver.minor),
432 ver.git_version))
433
434 LOG.info("Kubernetes server version is stored to {0}:\n{1}"
435 .format(env_file_path, env_version))
436 with open(env_file_path, 'w') as kver:
437 kver.write(env_version)
438
Vladimir Jigulina6b018b2018-07-18 15:19:01 +0400439
Vladimir Jigulin4ad52a82018-08-12 05:51:30 +0400440class K8SKubectlCli(object):
441 """ Contain kubectl cli commands and api wrappers"""
442 def __init__(self, manager):
443 self._manager = manager
Vladimir Jigulina6b018b2018-07-18 15:19:01 +0400444
Vladimir Jigulin4ad52a82018-08-12 05:51:30 +0400445 def cli_run(self, namespace, name, image, port, replicas=1):
446 cmd = "kubectl -n {0} run {1} --image={2} --port={3} --replicas={4}".\
447 format(namespace, name, image, port, replicas)
448 return self._manager.controller_check_call(cmd)
Vladimir Jigulina6b018b2018-07-18 15:19:01 +0400449
Vladimir Jigulin4ad52a82018-08-12 05:51:30 +0400450 def run(self, namespace, name, image, port, replicas=1):
451 self.cli_run(namespace, name, image, port, replicas)
452 return self._manager.api.deployments.get(
453 namespace=namespace, name=name)
Vladimir Jigulina6b018b2018-07-18 15:19:01 +0400454
Vladimir Jigulin4ad52a82018-08-12 05:51:30 +0400455 def cli_expose(self, namespace, resource_type, resource_name,
456 service_name=None, port='', service_type='ClusterIP'):
457 cmd = "kubectl -n {0} expose {1} {2} --port={3} --type={4}".format(
458 namespace, resource_type, resource_name, port, service_type)
459 if service_name is not None:
460 cmd += " --name={}".format(service_name)
461 return self._manager.controller_check_call(cmd)
Vladimir Jigulina6b018b2018-07-18 15:19:01 +0400462
Vladimir Jigulin4ad52a82018-08-12 05:51:30 +0400463 def expose(self, resource, service_name=None,
464 port='', service_type='ClusterIP'):
465 self.cli_expose(resource.namespace, resource.resource_type,
466 resource.name, service_name=service_name,
467 port=port, service_type=service_type)
468 return self._manager.api.services.get(
469 namespace=resource.namespace, name=service_name or resource.name)
470
471 def cli_exec(self, namespace, pod_name, cmd, container=''):
472 kubectl_cmd = "kubectl -n {0} exec --container={1} {2} -- {3}".format(
473 namespace, container, pod_name, cmd)
474 return self._manager.controller_check_call(kubectl_cmd)
475
476 # def exec(...), except exec is statement in python
477 def execute(self, pod, cmd, container=''):
478 return self.cli_exec(pod.namespace, pod.name, cmd, container=container)
479
480 def cli_annotate(self, namespace, resource_type, resource_name,
481 annotations, overwrite=False):
482 cmd = "kubectl -n {0} annotate {1} {2} {3}".format(
483 namespace, resource_type, resource_name, annotations)
484 if overwrite:
485 cmd += " --overwrite"
486 return self._manager.controller_check_call(cmd)
487
488 def annotate(self, resource, annotations, overwrite=False):
489 return self.cli_annotate(resource.namespace, resource.resource_type,
490 resource.name, annotations,
491 overwrite=overwrite)
492
493
494class K8SVirtlet(object):
495 """ Contain virtlet-related methods"""
496 def __init__(self, manager, namespace='kube-system'):
497 self._manager = manager
498 self._namespace = namespace
499
500 def get_virtlet_node_pod(self, node_name):
501 for pod in self._manager.api.pods.list(
502 namespace=self._namespace, name_prefix='virtlet-'):
503 if pod.read().spec.node_name == node_name:
504 return pod
505 return None
506
507 def get_pod_dom_uuid(self, pod):
508 uuid_name_map = self.virtlet_execute(
509 pod.read().spec.node_name, 'virsh list --uuid --name')['stdout']
Vladimir Jigulin4ad52a82018-08-12 05:51:30 +0400510 for line in uuid_name_map:
511 if line.rstrip().endswith("-{}".format(pod.name)):
512 return line.split(" ")[0]
513 raise Exception("Cannot detect uuid for pod {}".format(pod.name))
514
515 def virsh_domstats(self, pod):
516 """ get dict of vm stats """
517 uuid = self.get_pod_dom_uuid(pod)
518 result = self.virtlet_execute(
519 pod.read().spec.node_name, 'virsh domstats {}'.format(uuid))
520 stats = dict()
521 for line in result['stdout']:
522 if '=' in line:
523 vals = line.strip().split('=')
524 stats[vals[0]] = vals[1]
525 return stats
526
527 def virtlet_execute(self, node_name, cmd, container='libvirt'):
528 """ execute command inside virtlet container """
529 pod = self.get_virtlet_node_pod(node_name)
530 return self._manager.kubectl.execute(pod, cmd, container)
531
532
533class K8SSampleDeployment(object):
534 """ Wrapper for deployment run=>expose=>check frequent combination """
535 def __init__(self, manager, name,
536 namespace=None,
537 image='gcr.io/google-samples/node-hello:1.0',
538 port=8080,
539 replicas=2):
540 namespace = namespace or manager.api.default_namespace
541
542 self._manager = manager
543 self._port = port
544 self._deployment = \
545 manager.kubectl.run(namespace, name,
546 image=image, port=port, replicas=replicas)
547 self._index = 1 # used to generate svc name
548 self._svc = None # hold last created svc
549
550 def wait_ready(self, timeout=300, interval=5):
551 self._deployment.wait_ready(timeout=timeout, interval=interval)
552 return self
553
554 def svc(self):
555 """ Return the last exposed service"""
556 return self._svc
557
558 def expose(self, service_type='ClusterIP'):
559 service_name = "{0}-s{1}".format(self._deployment.name, self._index)
Vladimir Jigulin90689152018-09-26 15:38:19 +0400560 self._index += 1
Vladimir Jigulin4ad52a82018-08-12 05:51:30 +0400561 self._svc = self._manager.kubectl.expose(
562 self._deployment, port=self._port,
563 service_name=service_name, service_type=service_type)
564 return self._svc
565
566 def curl(self, svc=None, external=False):
567 if svc is None:
568 svc = self.svc()
569 url = "http://{0}:{1}".format(svc.get_ip(external), self._port)
Vladimir Jigulina6b018b2018-07-18 15:19:01 +0400570 if external:
571 return requests.get(url).text
572 else:
Vladimir Jigulin4ad52a82018-08-12 05:51:30 +0400573 return self._manager.curl(url)
Vladimir Jigulina6b018b2018-07-18 15:19:01 +0400574
Vladimir Jigulin4ad52a82018-08-12 05:51:30 +0400575 def is_service_available(self, svc=None, external=False):
576 return "Hello Kubernetes!" in self.curl(svc, external=external)
Vladimir Jigulin90689152018-09-26 15:38:19 +0400577
578 def delete(self):
579 for svc in self._manager.api.services.list_all(
580 name_prefix="{}-s".format(self._deployment.name)):
581 svc.delete()
582 self._deployment.delete()