blob: d84451fd908cb41d7fd5ce8686ca1fbc63f8dc7c [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
Vladimir Jigulin4ad52a82018-08-12 05:51:30 +0400115 def controller_check_call(self, cmd, **kwargs):
116 """ Run command on controller and return result """
117 LOG.info("running cmd on k8s controller: {}".format(cmd))
Vladimir Jigulinee1faa52018-06-25 13:00:51 +0400118 return self.__underlay.check_call(
Vladimir Jigulin4ad52a82018-08-12 05:51:30 +0400119 cmd=cmd, node_name=self.controller_name, **kwargs)
Artem Panchenko501e67e2017-06-14 14:59:18 +0300120
Vladimir Jigulin4ad52a82018-08-12 05:51:30 +0400121 def get_keepalived_vip(self):
Vladimir Jigulin62bcf462018-05-28 18:17:01 +0400122 """
Vladimir Jigulin4ad52a82018-08-12 05:51:30 +0400123 Return k8s VIP IP address
Vladimir Jigulin62bcf462018-05-28 18:17:01 +0400124
Vladimir Jigulin4ad52a82018-08-12 05:51:30 +0400125 :return: str, IP address
Vladimir Jigulin62bcf462018-05-28 18:17:01 +0400126 """
Vladimir Jigulin4ad52a82018-08-12 05:51:30 +0400127 ctl_vip_pillar = self._salt.get_pillar(
128 tgt="I@kubernetes:control:enabled:True",
129 pillar="_param:cluster_vip_address")[0]
130 return ctl_vip_pillar.values()[0]
Vladimir Jigulin62bcf462018-05-28 18:17:01 +0400131
Vladimir Jigulin4ad52a82018-08-12 05:51:30 +0400132 def run_sample_deployment(self, name, **kwargs):
133 return K8SSampleDeployment(self, name, **kwargs)
Victor Ryzhenkin3ffa2b42017-10-05 16:38:44 +0400134
Vladimir Jigulin4ad52a82018-08-12 05:51:30 +0400135 def get_pod_ips_from_container(self, pod_name, exclude_local=True,
136 namespace='default'):
137 """ Get ips from container using 'ip a'
138 Required for cni-genie multi-cni cases
139
140 :return: list of IP adresses
Victor Ryzhenkin3ffa2b42017-10-05 16:38:44 +0400141 """
Vladimir Jigulin4ad52a82018-08-12 05:51:30 +0400142 cmd = "ip a|grep \"inet \"|awk '{{print $2}}'"
143 result = self.kubectl.cli_exec(namespace, pod_name, cmd)['stdout']
144 ips = [line.strip().split('/')[0] for line in result]
145 if exclude_local:
146 ips = [ip for ip in ips if not ip.startswith("127.")]
147 return ips
Victor Ryzhenkin3ffa2b42017-10-05 16:38:44 +0400148
Vladimir Jigulin4ad52a82018-08-12 05:51:30 +0400149 def update_k8s_version(self, tag):
Vladimir Jigulin62bcf462018-05-28 18:17:01 +0400150 """
151 Update k8s images tag version in cluster meta and apply required
152 for update states
153
154 :param tag: New version tag of k8s images
155 :return:
156 """
157 master_host = self.__config.salt.salt_master_host
158
159 def update_image_tag_meta(config, image_name):
160 image_old = config.get(image_name)
161 image_base = image_old.split(':')[0]
162 image_new = "{}:{}".format(image_base, tag)
163 LOG.info("Changing k8s '{0}' image cluster meta to '{1}'".format(
164 image_name, image_new))
165
166 with self.__underlay.remote(host=master_host) as r:
167 cmd = "salt-call reclass.cluster_meta_set" \
168 " name={0} value={1}".format(image_name, image_new)
169 r.check_call(cmd)
170 return image_new
171
172 cfg = self.__config
173
174 update_image_tag_meta(cfg.k8s_deploy, "kubernetes_hyperkube_image")
175 update_image_tag_meta(cfg.k8s_deploy, "kubernetes_pause_image")
176 cfg.k8s.k8s_conformance_image = update_image_tag_meta(
177 cfg.k8s, "k8s_conformance_image")
178
179 steps_path = cfg.k8s_deploy.k8s_update_steps_path
180 update_commands = self.__underlay.read_template(steps_path)
181 self.execute_commands(
182 update_commands, label="Updating kubernetes to '{}'".format(tag))
Vladimir Jigulinee1faa52018-06-25 13:00:51 +0400183
Vladimir Jigulin4ad52a82018-08-12 05:51:30 +0400184 def run_conformance(self, timeout=60*60, log_out='k8s_conformance.log',
185 raise_on_err=True, node_name=None,
186 api_server='http://127.0.0.1:8080'):
187 if node_name is None:
188 node_name = self.controller_name
189 cmd = "set -o pipefail; docker run --net=host " \
190 "-e API_SERVER='{api}' {image} | tee '{log}'".format(
191 api=api_server, log=log_out,
192 image=self.__config.k8s.k8s_conformance_image)
193 return self.__underlay.check_call(
194 cmd=cmd, node_name=node_name, timeout=timeout,
Dennis Dmitrieveb50ce12018-09-27 13:34:32 +0300195 raise_on_err=raise_on_err, verbose=True)
Vladimir Jigulin4ad52a82018-08-12 05:51:30 +0400196
197 def run_virtlet_conformance(self, timeout=60 * 120,
198 log_file='virtlet_conformance.log'):
199 if self.__config.k8s.run_extended_virtlet_conformance:
200 ci_image = "cloud-images.ubuntu.com/xenial/current/" \
201 "xenial-server-cloudimg-amd64-disk1.img"
202 cmd = ("set -o pipefail; "
203 "docker run --net=host {0} /virtlet-e2e-tests "
204 "-include-cloud-init-tests -junitOutput report.xml "
205 "-image {2} -sshuser ubuntu -memoryLimit 1024 "
206 "-alsologtostderr -cluster-url http://127.0.0.1:8080 "
207 "-ginkgo.focus '\[Conformance\]' "
208 "| tee {1}".format(
209 self.__config.k8s_deploy.kubernetes_virtlet_image,
210 log_file, ci_image))
211 else:
212 cmd = ("set -o pipefail; "
213 "docker run --net=host {0} /virtlet-e2e-tests "
214 "-junitOutput report.xml "
215 "-alsologtostderr -cluster-url http://127.0.0.1:8080 "
216 "-ginkgo.focus '\[Conformance\]' "
217 "| tee {1}".format(
218 self.__config.k8s_deploy.kubernetes_virtlet_image,
219 log_file))
220 LOG.info("Executing: {}".format(cmd))
221 with self.__underlay.remote(
222 node_name=self.controller_name) as remote:
223 result = remote.check_call(cmd, timeout=timeout)
224 stderr = result['stderr']
225 stdout = result['stdout']
226 LOG.info("Test results stdout: {}".format(stdout))
227 LOG.info("Test results stderr: {}".format(stderr))
228 return result
229
230 def start_k8s_cncf_verification(self, timeout=60 * 90):
Vladimir Jigulin0c8dd5a2018-08-28 05:08:35 +0400231 """
232 Build sonobuoy using golang docker image and install it in system
233 Then generate sonobuoy verification manifest using gen command
234 and wait for it to end using status annotation
235 TODO (vjigulin): All sonobuoy methods can be improved if we define
236 correct KUBECONFIG env variable
237 """
238 cncf_cmd =\
239 'docker run --network host --name sonobuoy golang:1.11 bash -c ' \
240 '"go get -d -v github.com/heptio/sonobuoy && ' \
241 'go install -v github.com/heptio/sonobuoy" && ' \
242 'docker cp sonobuoy:/go/bin/sonobuoy /usr/local/bin/ && ' \
243 'docker rm sonobuoy && ' \
244 'sonobuoy gen | kubectl apply -f -'
Vladimir Jigulin4ad52a82018-08-12 05:51:30 +0400245
Vladimir Jigulin0c8dd5a2018-08-28 05:08:35 +0400246 self.controller_check_call(cncf_cmd, timeout=900)
247
248 sonobuoy_pod = self.api.pods.get('sonobuoy', 'heptio-sonobuoy')
249 sonobuoy_pod.wait_running()
250
251 def sonobuoy_status():
252 annotations = sonobuoy_pod.read().metadata.annotations
253 json_status = annotations['sonobuoy.hept.io/status']
254 status = yaml.safe_load(json_status)['status']
255 if status != 'running':
256 LOG.info("CNCF status: {}".format(json_status))
257 return yaml.safe_load(json_status)['status']
258
259 LOG.info("Waiting for CNCF to complete")
260 helpers.wait(
261 lambda: sonobuoy_status() == 'complete',
262 interval=30, timeout=timeout,
263 timeout_msg="Timeout for CNCF reached."
264 )
Vladimir Jigulin4ad52a82018-08-12 05:51:30 +0400265
266 def extract_file_to_node(self, system='docker',
267 container='virtlet',
268 file_path='report.xml',
269 out_dir='.',
270 **kwargs):
Vladimir Jigulinee1faa52018-06-25 13:00:51 +0400271 """
Vladimir Jigulin4ad52a82018-08-12 05:51:30 +0400272 Download file from docker or k8s container to node
Vladimir Jigulinee1faa52018-06-25 13:00:51 +0400273
Vladimir Jigulin4ad52a82018-08-12 05:51:30 +0400274 :param system: docker or k8s
275 :param container: Full name of part of name
276 :param file_path: File path in container
277 :param kwargs: Used to control pod and namespace
278 :param out_dir: Output directory
279 :return:
Vladimir Jigulinee1faa52018-06-25 13:00:51 +0400280 """
Vladimir Jigulin0c8dd5a2018-08-28 05:08:35 +0400281 with self.__underlay.remote(node_name=self.controller_name) as remote:
Vladimir Jigulin4ad52a82018-08-12 05:51:30 +0400282 if system is 'docker':
283 cmd = ("docker ps --all | grep \"{0}\" |"
284 " awk '{{print $1}}'".format(container))
285 result = remote.check_call(cmd, raise_on_err=False)
286 if result['stdout']:
287 container_id = result['stdout'][0].strip()
288 else:
289 LOG.info('No container found, skipping extraction...')
290 return
291 cmd = "docker start {}".format(container_id)
292 remote.check_call(cmd, raise_on_err=False)
293 cmd = "docker cp \"{0}:/{1}\" \"{2}\"".format(
294 container_id, file_path, out_dir)
295 remote.check_call(cmd, raise_on_err=False)
296 else:
297 # system is k8s
298 pod_name = kwargs.get('pod_name')
299 pod_namespace = kwargs.get('pod_namespace')
300 cmd = 'kubectl cp \"{0}/{1}:/{2}\" \"{3}\"'.format(
301 pod_namespace, pod_name, file_path, out_dir)
302 remote.check_call(cmd, raise_on_err=False)
Vladimir Jigulina6b018b2018-07-18 15:19:01 +0400303
Vladimir Jigulin4ad52a82018-08-12 05:51:30 +0400304 def download_k8s_logs(self, files):
305 """
306 Download JUnit report and conformance logs from cluster
307 :param files:
308 :return:
309 """
310 master_host = self.__config.salt.salt_master_host
311 with self.__underlay.remote(host=master_host) as r:
312 for log_file in files:
313 cmd = "rsync -r \"{0}:/root/{1}\" /root/".format(
314 self.controller_name, log_file)
315 r.check_call(cmd, raise_on_err=False)
316 LOG.info("Downloading the artifact {0}".format(log_file))
317 r.download(destination=log_file, target=os.getcwd())
Dennis Dmitriev445b4322018-10-08 14:32:07 +0300318 self.store_server_version(os.path.join(os.getcwd(), 'env_k8s_version'))
Vladimir Jigulina6b018b2018-07-18 15:19:01 +0400319
Vladimir Jigulin4ad52a82018-08-12 05:51:30 +0400320 def combine_xunit(self, path, output):
321 """
322 Function to combine multiple xmls with test results to
323 one.
Vladimir Jigulin34dfa942018-07-23 21:05:48 +0400324
Vladimir Jigulin4ad52a82018-08-12 05:51:30 +0400325 :param path: Path where xmls to combine located
326 :param output: Path to xml file where output will stored
327 :return:
328 """
329 with self.__underlay.remote(node_name=self.controller_name) as r:
330 cmd = ("apt-get install python-setuptools -y; "
331 "pip install "
332 "https://github.com/mogaika/xunitmerge/archive/master.zip")
333 LOG.debug('Installing xunitmerge')
334 r.check_call(cmd, raise_on_err=False)
335 LOG.debug('Merging xunit')
Dennis Dmitrievee5ef232018-08-31 13:53:18 +0300336 cmd = ("cd {0}; arg=''; "
Vladimir Jigulin4ad52a82018-08-12 05:51:30 +0400337 "for i in $(ls | grep xml); "
338 "do arg=\"$arg $i\"; done && "
339 "xunitmerge $arg {1}".format(path, output))
340 r.check_call(cmd, raise_on_err=False)
Vladimir Jigulin34dfa942018-07-23 21:05:48 +0400341
Vladimir Jigulin4ad52a82018-08-12 05:51:30 +0400342 def manage_cncf_archive(self):
343 """
Vladimir Jigulin0c8dd5a2018-08-28 05:08:35 +0400344 Function to untar archive, move files that we are needs to the
345 home folder, prepare it to downloading.
346 Will generate files on controller node:
347 e2e.log, junit_01.xml, cncf_results.tar.gz, version.txt
Vladimir Jigulin4ad52a82018-08-12 05:51:30 +0400348 :return:
349 """
350
Vladimir Jigulin0c8dd5a2018-08-28 05:08:35 +0400351 cmd =\
352 "rm -rf cncf_results.tar.gz result && " \
353 "mkdir result && " \
354 "mv *_sonobuoy_*.tar.gz cncf_results.tar.gz && " \
355 "tar -C result -xzf cncf_results.tar.gz && " \
356 "mv result/plugins/e2e/results/e2e.log . ; " \
357 "mv result/plugins/e2e/results/junit_01.xml . ; " \
358 "kubectl version > version.txt"
Vladimir Jigulin4ad52a82018-08-12 05:51:30 +0400359
Vladimir Jigulin0c8dd5a2018-08-28 05:08:35 +0400360 self.controller_check_call(cmd, raise_on_err=False)
Vladimir Jigulin4ad52a82018-08-12 05:51:30 +0400361
362 @retry(300, exception=DevopsCalledProcessError)
363 def nslookup(self, host, src):
364 """ Run nslookup on controller and return result """
365 return self.controller_check_call("nslookup {0} {1}".format(host, src))
366
367 @retry(300, exception=DevopsCalledProcessError)
Vladimir Jigulin57ecae92018-09-10 22:51:15 +0400368 def curl(self, url, *args):
Vladimir Jigulin4ad52a82018-08-12 05:51:30 +0400369 """
370 Run curl on controller and return stdout
371
372 :param url: url to curl
Vladimir Jigulin57ecae92018-09-10 22:51:15 +0400373 :return: list of strings (with /n at end of every line)
Vladimir Jigulin4ad52a82018-08-12 05:51:30 +0400374 """
Vladimir Jigulin57ecae92018-09-10 22:51:15 +0400375 args = list(args)
376 args.append(url)
377 cmd = "curl -s -S {}".format(
378 " ".join(["'{}'".format(a.replace("'", "\\'")) for a in args]))
379 result = self.controller_check_call(cmd)
380 LOG.debug("{0}\nresult:\n{1}".format(cmd, result['stdout']))
Vladimir Jigulin4ad52a82018-08-12 05:51:30 +0400381 return result['stdout']
Vladimir Jigulin34dfa942018-07-23 21:05:48 +0400382
Dennis Dmitriev445b4322018-10-08 14:32:07 +0300383 def store_server_version(self, env_file_path):
384 """Store Kubernetes server version in bash source file"""
385
386 def digits(string):
387 return ''.join(n for n in string if n.isdigit())
388
389 ver = self.api.api_version.get_code()
390 LOG.debug("Got Kubernetes server version:\n{0}".format(ver))
391
392 env_version = ("export KUBE_SERVER_VERSION={0}.{1}\n"
393 "export KUBE_SERVER_GIT_VERSION={2}\n"
394 .format(digits(ver.major),
395 digits(ver.minor),
396 ver.git_version))
397
398 LOG.info("Kubernetes server version is stored to {0}:\n{1}"
399 .format(env_file_path, env_version))
400 with open(env_file_path, 'w') as kver:
401 kver.write(env_version)
402
Vladimir Jigulina6b018b2018-07-18 15:19:01 +0400403
Vladimir Jigulin4ad52a82018-08-12 05:51:30 +0400404class K8SKubectlCli(object):
405 """ Contain kubectl cli commands and api wrappers"""
406 def __init__(self, manager):
407 self._manager = manager
Vladimir Jigulina6b018b2018-07-18 15:19:01 +0400408
Vladimir Jigulin4ad52a82018-08-12 05:51:30 +0400409 def cli_run(self, namespace, name, image, port, replicas=1):
410 cmd = "kubectl -n {0} run {1} --image={2} --port={3} --replicas={4}".\
411 format(namespace, name, image, port, replicas)
412 return self._manager.controller_check_call(cmd)
Vladimir Jigulina6b018b2018-07-18 15:19:01 +0400413
Vladimir Jigulin4ad52a82018-08-12 05:51:30 +0400414 def run(self, namespace, name, image, port, replicas=1):
415 self.cli_run(namespace, name, image, port, replicas)
416 return self._manager.api.deployments.get(
417 namespace=namespace, name=name)
Vladimir Jigulina6b018b2018-07-18 15:19:01 +0400418
Vladimir Jigulin4ad52a82018-08-12 05:51:30 +0400419 def cli_expose(self, namespace, resource_type, resource_name,
420 service_name=None, port='', service_type='ClusterIP'):
421 cmd = "kubectl -n {0} expose {1} {2} --port={3} --type={4}".format(
422 namespace, resource_type, resource_name, port, service_type)
423 if service_name is not None:
424 cmd += " --name={}".format(service_name)
425 return self._manager.controller_check_call(cmd)
Vladimir Jigulina6b018b2018-07-18 15:19:01 +0400426
Vladimir Jigulin4ad52a82018-08-12 05:51:30 +0400427 def expose(self, resource, service_name=None,
428 port='', service_type='ClusterIP'):
429 self.cli_expose(resource.namespace, resource.resource_type,
430 resource.name, service_name=service_name,
431 port=port, service_type=service_type)
432 return self._manager.api.services.get(
433 namespace=resource.namespace, name=service_name or resource.name)
434
435 def cli_exec(self, namespace, pod_name, cmd, container=''):
436 kubectl_cmd = "kubectl -n {0} exec --container={1} {2} -- {3}".format(
437 namespace, container, pod_name, cmd)
438 return self._manager.controller_check_call(kubectl_cmd)
439
440 # def exec(...), except exec is statement in python
441 def execute(self, pod, cmd, container=''):
442 return self.cli_exec(pod.namespace, pod.name, cmd, container=container)
443
444 def cli_annotate(self, namespace, resource_type, resource_name,
445 annotations, overwrite=False):
446 cmd = "kubectl -n {0} annotate {1} {2} {3}".format(
447 namespace, resource_type, resource_name, annotations)
448 if overwrite:
449 cmd += " --overwrite"
450 return self._manager.controller_check_call(cmd)
451
452 def annotate(self, resource, annotations, overwrite=False):
453 return self.cli_annotate(resource.namespace, resource.resource_type,
454 resource.name, annotations,
455 overwrite=overwrite)
456
457
458class K8SVirtlet(object):
459 """ Contain virtlet-related methods"""
460 def __init__(self, manager, namespace='kube-system'):
461 self._manager = manager
462 self._namespace = namespace
463
464 def get_virtlet_node_pod(self, node_name):
465 for pod in self._manager.api.pods.list(
466 namespace=self._namespace, name_prefix='virtlet-'):
467 if pod.read().spec.node_name == node_name:
468 return pod
469 return None
470
471 def get_pod_dom_uuid(self, pod):
472 uuid_name_map = self.virtlet_execute(
473 pod.read().spec.node_name, 'virsh list --uuid --name')['stdout']
Vladimir Jigulin4ad52a82018-08-12 05:51:30 +0400474 for line in uuid_name_map:
475 if line.rstrip().endswith("-{}".format(pod.name)):
476 return line.split(" ")[0]
477 raise Exception("Cannot detect uuid for pod {}".format(pod.name))
478
479 def virsh_domstats(self, pod):
480 """ get dict of vm stats """
481 uuid = self.get_pod_dom_uuid(pod)
482 result = self.virtlet_execute(
483 pod.read().spec.node_name, 'virsh domstats {}'.format(uuid))
484 stats = dict()
485 for line in result['stdout']:
486 if '=' in line:
487 vals = line.strip().split('=')
488 stats[vals[0]] = vals[1]
489 return stats
490
491 def virtlet_execute(self, node_name, cmd, container='libvirt'):
492 """ execute command inside virtlet container """
493 pod = self.get_virtlet_node_pod(node_name)
494 return self._manager.kubectl.execute(pod, cmd, container)
495
496
497class K8SSampleDeployment(object):
498 """ Wrapper for deployment run=>expose=>check frequent combination """
499 def __init__(self, manager, name,
500 namespace=None,
501 image='gcr.io/google-samples/node-hello:1.0',
502 port=8080,
503 replicas=2):
504 namespace = namespace or manager.api.default_namespace
505
506 self._manager = manager
507 self._port = port
508 self._deployment = \
509 manager.kubectl.run(namespace, name,
510 image=image, port=port, replicas=replicas)
511 self._index = 1 # used to generate svc name
512 self._svc = None # hold last created svc
513
514 def wait_ready(self, timeout=300, interval=5):
515 self._deployment.wait_ready(timeout=timeout, interval=interval)
516 return self
517
518 def svc(self):
519 """ Return the last exposed service"""
520 return self._svc
521
522 def expose(self, service_type='ClusterIP'):
523 service_name = "{0}-s{1}".format(self._deployment.name, self._index)
Vladimir Jigulin90689152018-09-26 15:38:19 +0400524 self._index += 1
Vladimir Jigulin4ad52a82018-08-12 05:51:30 +0400525 self._svc = self._manager.kubectl.expose(
526 self._deployment, port=self._port,
527 service_name=service_name, service_type=service_type)
528 return self._svc
529
530 def curl(self, svc=None, external=False):
531 if svc is None:
532 svc = self.svc()
533 url = "http://{0}:{1}".format(svc.get_ip(external), self._port)
Vladimir Jigulina6b018b2018-07-18 15:19:01 +0400534 if external:
535 return requests.get(url).text
536 else:
Vladimir Jigulin4ad52a82018-08-12 05:51:30 +0400537 return self._manager.curl(url)
Vladimir Jigulina6b018b2018-07-18 15:19:01 +0400538
Vladimir Jigulin4ad52a82018-08-12 05:51:30 +0400539 def is_service_available(self, svc=None, external=False):
540 return "Hello Kubernetes!" in self.curl(svc, external=external)
Vladimir Jigulin90689152018-09-26 15:38:19 +0400541
542 def delete(self):
543 for svc in self._manager.api.services.list_all(
544 name_prefix="{}-s".format(self._deployment.name)):
545 svc.delete()
546 self._deployment.delete()