blob: d84fd73660ad274f653c4b2de0740918870da4f6 [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
15import time
16
17import yaml
18
19from devops.helpers import helpers
20
21from tcp_tests import logger
22from tcp_tests.managers.execute_commands import ExecuteCommandsMixin
23from tcp_tests.managers.k8s import cluster
Sergey Vasilenkofd1fd612017-09-20 13:09:51 +030024from k8sclient.client.rest import ApiException
Artem Panchenko0594cd72017-06-12 13:25:26 +030025
26LOG = logger.logger
27
28
29class K8SManager(ExecuteCommandsMixin):
30 """docstring for K8SManager"""
31
32 __config = None
33 __underlay = None
34
35 def __init__(self, config, underlay, salt):
36 self.__config = config
37 self.__underlay = underlay
38 self._salt = salt
39 self._api_client = None
40 super(K8SManager, self).__init__(
41 config=config, underlay=underlay)
42
43 def install(self, commands):
44 self.execute_commands(commands,
45 label='Install Kubernetes services')
46 self.__config.k8s.k8s_installed = True
47 self.__config.k8s.kube_host = self.get_proxy_api()
48
49 def get_proxy_api(self):
50 k8s_proxy_ip_pillars = self._salt.get_pillar(
vrovachev99228d32017-06-08 19:46:10 +040051 tgt='I@haproxy:proxy:enabled:true and I@kubernetes:master',
Artem Panchenko0594cd72017-06-12 13:25:26 +030052 pillar='haproxy:proxy:listen:k8s_secure:binds:address')
vrovachev99228d32017-06-08 19:46:10 +040053 k8s_hosts = self._salt.get_pillar(
54 tgt='I@haproxy:proxy:enabled:true and I@kubernetes:master',
55 pillar='kubernetes:pool:apiserver:host')
Artem Panchenko0594cd72017-06-12 13:25:26 +030056 k8s_proxy_ip = set([ip
57 for item in k8s_proxy_ip_pillars
Dina Belovae6fdffb2017-09-19 13:58:34 -070058 for node, ip in item.items() if ip])
vrovachev99228d32017-06-08 19:46:10 +040059 k8s_hosts = set([ip
Dina Belovae6fdffb2017-09-19 13:58:34 -070060 for item in k8s_hosts
61 for node, ip in item.items() if ip])
vrovachev99228d32017-06-08 19:46:10 +040062 assert len(k8s_hosts) == 1, (
63 "Found more than one Kubernetes API hosts in pillars:{0}, "
64 "expected one!").format(k8s_hosts)
65 k8s_host = k8s_hosts.pop()
66 assert k8s_host in k8s_proxy_ip, (
67 "Kubernetes API host:{0} not found in proxies:{} "
68 "on k8s master nodes. K8s proxies are expected on "
69 "nodes with K8s master").format(k8s_host, k8s_proxy_ip)
70 return k8s_host
Artem Panchenko0594cd72017-06-12 13:25:26 +030071
72 @property
73 def api(self):
74 if self._api_client is None:
75 self._api_client = cluster.K8sCluster(
76 user=self.__config.k8s_deploy.kubernetes_admin_user,
77 password=self.__config.k8s_deploy.kubernetes_admin_password,
78 host=self.__config.k8s.kube_host,
79 port=self.__config.k8s.kube_apiserver_port,
80 default_namespace='default')
81 return self._api_client
82
83 def get_pod_phase(self, pod_name, namespace=None):
84 return self.api.pods.get(
85 name=pod_name, namespace=namespace).phase
86
87 def wait_pod_phase(self, pod_name, phase, namespace=None, timeout=60):
88 """Wait phase of pod_name from namespace while timeout
89
90 :param str: pod_name
91 :param str: namespace
92 :param list or str: phase
93 :param int: timeout
94
95 :rtype: None
96 """
97 if isinstance(phase, str):
98 phase = [phase]
99
100 def check():
101 return self.get_pod_phase(pod_name, namespace) in phase
102
103 helpers.wait(check, timeout=timeout,
104 timeout_msg='Timeout waiting, pod {pod_name} is not in '
105 '"{phase}" phase'.format(
106 pod_name=pod_name, phase=phase))
107
108 def wait_pods_phase(self, pods, phase, timeout=60):
109 """Wait timeout seconds for phase of pods
110
111 :param pods: list of K8sPod
112 :param phase: list or str
113 :param timeout: int
114
115 :rtype: None
116 """
117 if isinstance(phase, str):
118 phase = [phase]
119
120 def check(pod_name, namespace):
121 return self.get_pod_phase(pod_name, namespace) in phase
122
123 def check_all_pods():
124 return all(check(pod.name, pod.metadata.namespace) for pod in pods)
125
126 helpers.wait(
127 check_all_pods,
128 timeout=timeout,
129 timeout_msg='Timeout waiting, pods {0} are not in "{1}" '
130 'phase'.format([pod.name for pod in pods], phase))
131
132 def check_pod_create(self, body, namespace=None, timeout=300, interval=5):
133 """Check creating sample pod
134
135 :param k8s_pod: V1Pod
136 :param namespace: str
137 :rtype: V1Pod
138 """
139 LOG.info("Creating pod in k8s cluster")
140 LOG.debug(
141 "POD spec to create:\n{}".format(
142 yaml.dump(body, default_flow_style=False))
143 )
144 LOG.debug("Timeout for creation is set to {}".format(timeout))
145 LOG.debug("Checking interval is set to {}".format(interval))
146 pod = self.api.pods.create(body=body, namespace=namespace)
147 pod.wait_running(timeout=300, interval=5)
148 LOG.info("Pod '{0}' is created in '{1}' namespace".format(
149 pod.name, pod.namespace))
150 return self.api.pods.get(name=pod.name, namespace=pod.namespace)
151
152 def wait_pod_deleted(self, podname, timeout=60, interval=5):
153 helpers.wait(
154 lambda: podname not in [pod.name for pod in self.api.pods.list()],
155 timeout=timeout,
156 interval=interval,
157 timeout_msg="Pod deletion timeout reached!"
158 )
159
160 def check_pod_delete(self, k8s_pod, timeout=300, interval=5,
161 namespace=None):
162 """Deleting pod from k8s
163
164 :param k8s_pod: tcp_tests.managers.k8s.nodes.K8sNode
165 :param k8sclient: tcp_tests.managers.k8s.cluster.K8sCluster
166 """
167 LOG.info("Deleting pod '{}'".format(k8s_pod.name))
168 LOG.debug("Pod status:\n{}".format(k8s_pod.status))
169 LOG.debug("Timeout for deletion is set to {}".format(timeout))
170 LOG.debug("Checking interval is set to {}".format(interval))
171 self.api.pods.delete(body=k8s_pod, name=k8s_pod.name,
172 namespace=namespace)
173 self.wait_pod_deleted(k8s_pod.name, timeout, interval)
174 LOG.debug("Pod '{}' is deleted".format(k8s_pod.name))
175
176 def check_service_create(self, body, namespace=None):
177 """Check creating k8s service
178
179 :param body: dict, service spec
180 :param namespace: str
181 :rtype: K8sService object
182 """
183 LOG.info("Creating service in k8s cluster")
184 LOG.debug(
185 "Service spec to create:\n{}".format(
186 yaml.dump(body, default_flow_style=False))
187 )
188 service = self.api.services.create(body=body, namespace=namespace)
189 LOG.info("Service '{0}' is created in '{1}' namespace".format(
190 service.name, service.namespace))
191 return self.api.services.get(name=service.name,
192 namespace=service.namespace)
193
194 def check_ds_create(self, body, namespace=None):
195 """Check creating k8s DaemonSet
196
197 :param body: dict, DaemonSet spec
198 :param namespace: str
199 :rtype: K8sDaemonSet object
200 """
201 LOG.info("Creating DaemonSet in k8s cluster")
202 LOG.debug(
203 "DaemonSet spec to create:\n{}".format(
204 yaml.dump(body, default_flow_style=False))
205 )
206 ds = self.api.daemonsets.create(body=body, namespace=namespace)
207 LOG.info("DaemonSet '{0}' is created in '{1}' namespace".format(
208 ds.name, ds.namespace))
209 return self.api.daemonsets.get(name=ds.name, namespace=ds.namespace)
210
211 def check_ds_ready(self, dsname, namespace=None):
212 """Check if k8s DaemonSet is ready
213
214 :param dsname: str, ds name
215 :return: bool
216 """
217 ds = self.api.daemonsets.get(name=dsname, namespace=namespace)
218 return (ds.status.current_number_scheduled ==
219 ds.status.desired_number_scheduled)
220
221 def wait_ds_ready(self, dsname, namespace=None, timeout=60, interval=5):
222 """Wait until all pods are scheduled on nodes
223
224 :param dsname: str, ds name
225 :param timeout: int
226 :param interval: int
227 """
228 helpers.wait(
229 lambda: self.check_ds_ready(dsname, namespace=namespace),
230 timeout=timeout, interval=interval)
231
Artem Panchenko501e67e2017-06-14 14:59:18 +0300232 def check_deploy_create(self, body, namespace=None):
233 """Check creating k8s Deployment
234
235 :param body: dict, Deployment spec
236 :param namespace: str
237 :rtype: K8sDeployment object
238 """
239 LOG.info("Creating Deployment in k8s cluster")
240 LOG.debug(
241 "Deployment spec to create:\n{}".format(
242 yaml.dump(body, default_flow_style=False))
243 )
244 deploy = self.api.deployments.create(body=body, namespace=namespace)
245 LOG.info("Deployment '{0}' is created in '{1}' namespace".format(
246 deploy.name, deploy.namespace))
247 return self.api.deployments.get(name=deploy.name,
248 namespace=deploy.namespace)
249
250 def check_deploy_ready(self, deploy_name, namespace=None):
251 """Check if k8s Deployment is ready
252
253 :param deploy_name: str, deploy name
254 :return: bool
255 """
Dina Belovae6fdffb2017-09-19 13:58:34 -0700256 deploy = self.api.deployments.get(name=deploy_name,
257 namespace=namespace)
Artem Panchenko501e67e2017-06-14 14:59:18 +0300258 return deploy.status.available_replicas == deploy.status.replicas
259
Dina Belovae6fdffb2017-09-19 13:58:34 -0700260 def wait_deploy_ready(self, deploy_name, namespace=None, timeout=60,
261 interval=5):
Artem Panchenko501e67e2017-06-14 14:59:18 +0300262 """Wait until all pods are scheduled on nodes
263
264 :param deploy_name: str, deploy name
265 :param timeout: int
266 :param interval: int
267 """
268 helpers.wait(
269 lambda: self.check_deploy_ready(deploy_name, namespace=namespace),
270 timeout=timeout, interval=interval)
271
Artem Panchenko0594cd72017-06-12 13:25:26 +0300272 def check_namespace_create(self, name):
273 """Check creating k8s Namespace
274
275 :param name: str
276 :rtype: K8sNamespace object
277 """
Sergey Vasilenkofd1fd612017-09-20 13:09:51 +0300278 try:
279 ns = self.api.namespaces.get(name=name)
280 LOG.info("Namespace '{0}' is already exists".format(ns.name))
281 except ApiException as e:
282 if hasattr(e,"status") and 404 == e.status:
283 LOG.info("Creating Namespace in k8s cluster")
284 ns = self.api.namespaces.create(body={'metadata': {'name': name}})
285 LOG.info("Namespace '{0}' is created".format(ns.name))
286 # wait 10 seconds until a token for new service account is created
287 time.sleep(10)
288 ns = self.api.namespaces.get(name=ns.name)
289 else:
290 raise
291 return ns
Artem Panchenko0594cd72017-06-12 13:25:26 +0300292
293 def create_objects(self, path):
294 if isinstance(path, str):
295 path = [path]
296 params = ' '.join(["-f {}".format(p) for p in path])
297 cmd = 'kubectl create {params}'.format(params=params)
298 with self.__underlay.remote(
299 host=self.__config.k8s.kube_host) as remote:
300 LOG.info("Running command '{cmd}' on node {node}".format(
301 cmd=cmd,
302 node=remote.hostname)
303 )
304 result = remote.check_call(cmd)
305 LOG.info(result['stdout'])
306
307 def get_running_pods(self, pod_name, namespace=None):
308 pods = [pod for pod in self.api.pods.list(namespace=namespace)
309 if (pod_name in pod.name and pod.status.phase == 'Running')]
310 return pods
311
312 def get_pods_number(self, pod_name, namespace=None):
313 pods = self.get_running_pods(pod_name, namespace)
314 return len(pods)
315
316 def get_running_pods_by_ssh(self, pod_name, namespace=None):
317 with self.__underlay.remote(
318 host=self.__config.k8s.kube_host) as remote:
319 result = remote.check_call("kubectl get pods --namespace {} |"
320 " grep {} | awk '{{print $1 \" \""
321 " $3}}'".format(namespace,
322 pod_name))['stdout']
323 running_pods = [data.strip().split()[0] for data in result
324 if data.strip().split()[1] == 'Running']
325 return running_pods
326
327 def get_pods_restarts(self, pod_name, namespace=None):
328 pods = [pod.status.container_statuses[0].restart_count
329 for pod in self.get_running_pods(pod_name, namespace)]
330 return sum(pods)
vrovacheva9d08332017-06-22 20:01:59 +0400331
332 def run_conformance(self, timeout=60 * 60):
333 with self.__underlay.remote(
334 host=self.__config.k8s.kube_host) as remote:
335 result = remote.check_call(
336 "docker run --rm --net=host -e API_SERVER="
337 "'http://127.0.0.1:8080' {}".format(
338 self.__config.k8s.k8s_conformance_image),
339 timeout=timeout)['stdout']
340 return result
Artem Panchenko501e67e2017-06-14 14:59:18 +0300341
342 def get_k8s_masters(self):
343 k8s_masters_fqdn = self._salt.get_pillar(tgt='I@kubernetes:master',
344 pillar='linux:network:fqdn')
345 return [self._K8SManager__underlay.host_by_node_name(node_name=v)
346 for pillar in k8s_masters_fqdn for k, v in pillar.items()]
Victor Ryzhenkin14354ac2017-09-27 17:42:30 +0400347
348 def kubectl_run(self, name, image, port):
349 with self.__underlay.remote(
350 host=self.__config.k8s.kube_host) as remote:
351 result = remote.check_call(
352 "kubectl run {0} --image={1} --port={2}".format(
353 name, image, port
354 )
355 )
356 return result
357
358 def kubectl_expose(self, resource, name, port, type):
359 with self.__underlay.remote(
360 host=self.__config.k8s.kube_host) as remote:
361 result = remote.check_call(
362 "kubectl expose {0} {1} --port={2} --type={3}".format(
363 resource, name, port, type
364 )
365 )
366 return result
367
368 def kubectl_annotate(self, resource, name, annotaion):
369 with self.__underlay.remote(
370 host=self.__config.k8s.kube_host) as remote:
371 result = remote.check_call(
372 "kubectl annotate {0} {1} {3}".format(
373 resource, name, annotaion
374 )
375 )
376 return result
377
378 def get_svc_ip(self, name):
379 with self.__underlay.remote(
380 host=self.__config.k8s.kube_host) as remote:
381 result = remote.check_call(
382 "kubectl get svc --all-namespaces | grep {0} | "
383 "awk '{{print $2}}'".format(name)
384 )
385 return result['stdout'][0].strip()
386
387 def nslookup(self, host, src):
388 with self.__underlay.remote(
389 host=self.__config.k8s.kube_host) as remote:
390 remote.check_call("nslookup {0} {1}".format(host, src))
391