| Artem Panchenko | 0594cd7 | 2017-06-12 13:25:26 +0300 | [diff] [blame] | 1 | #    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 Ryzhenkin | 8ff3c3f | 2018-01-17 19:37:05 +0400 | [diff] [blame] | 15 | import os | 
| Vladimir Jigulin | a6b018b | 2018-07-18 15:19:01 +0400 | [diff] [blame] | 16 | import requests | 
| Vladimir Jigulin | 0c8dd5a | 2018-08-28 05:08:35 +0400 | [diff] [blame] | 17 | import yaml | 
| Artem Panchenko | 0594cd7 | 2017-06-12 13:25:26 +0300 | [diff] [blame] | 18 |  | 
 | 19 | from devops.helpers import helpers | 
| Victor Ryzhenkin | 66d3937 | 2017-09-28 19:25:48 +0400 | [diff] [blame] | 20 | from devops.error import DevopsCalledProcessError | 
| Artem Panchenko | 0594cd7 | 2017-06-12 13:25:26 +0300 | [diff] [blame] | 21 |  | 
 | 22 | from tcp_tests import logger | 
| Victor Ryzhenkin | 66d3937 | 2017-09-28 19:25:48 +0400 | [diff] [blame] | 23 | from tcp_tests.helpers import ext | 
 | 24 | from tcp_tests.helpers.utils import retry | 
| Artem Panchenko | 0594cd7 | 2017-06-12 13:25:26 +0300 | [diff] [blame] | 25 | from tcp_tests.managers.execute_commands import ExecuteCommandsMixin | 
 | 26 | from tcp_tests.managers.k8s import cluster | 
| Artem Panchenko | 0594cd7 | 2017-06-12 13:25:26 +0300 | [diff] [blame] | 27 |  | 
 | 28 | LOG = logger.logger | 
 | 29 |  | 
 | 30 |  | 
 | 31 | class 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 Jigulin | 4ad52a8 | 2018-08-12 05:51:30 +0400 | [diff] [blame] | 41 |         self._api = None | 
 | 42 |         self.kubectl = K8SKubectlCli(self) | 
 | 43 |         self.virtlet = K8SVirtlet(self) | 
 | 44 |         super(K8SManager, self).__init__(config=config, underlay=underlay) | 
| Artem Panchenko | 0594cd7 | 2017-06-12 13:25:26 +0300 | [diff] [blame] | 45 |  | 
 | 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( | 
| vrovachev | 99228d3 | 2017-06-08 19:46:10 +0400 | [diff] [blame] | 54 |             tgt='I@haproxy:proxy:enabled:true and I@kubernetes:master', | 
| Artem Panchenko | 0594cd7 | 2017-06-12 13:25:26 +0300 | [diff] [blame] | 55 |             pillar='haproxy:proxy:listen:k8s_secure:binds:address') | 
| vrovachev | 99228d3 | 2017-06-08 19:46:10 +0400 | [diff] [blame] | 56 |         k8s_hosts = self._salt.get_pillar( | 
 | 57 |             tgt='I@haproxy:proxy:enabled:true and I@kubernetes:master', | 
 | 58 |             pillar='kubernetes:pool:apiserver:host') | 
| Artem Panchenko | 0594cd7 | 2017-06-12 13:25:26 +0300 | [diff] [blame] | 59 |         k8s_proxy_ip = set([ip | 
 | 60 |                             for item in k8s_proxy_ip_pillars | 
| Dina Belova | e6fdffb | 2017-09-19 13:58:34 -0700 | [diff] [blame] | 61 |                             for node, ip in item.items() if ip]) | 
| vrovachev | 99228d3 | 2017-06-08 19:46:10 +0400 | [diff] [blame] | 62 |         k8s_hosts = set([ip | 
| Dina Belova | e6fdffb | 2017-09-19 13:58:34 -0700 | [diff] [blame] | 63 |                          for item in k8s_hosts | 
 | 64 |                          for node, ip in item.items() if ip]) | 
| vrovachev | 99228d3 | 2017-06-08 19:46:10 +0400 | [diff] [blame] | 65 |         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 Panchenko | 0594cd7 | 2017-06-12 13:25:26 +0300 | [diff] [blame] | 74 |  | 
| Vladimir Jigulin | 4ad52a8 | 2018-08-12 05:51:30 +0400 | [diff] [blame] | 75 |     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 Panchenko | 0594cd7 | 2017-06-12 13:25:26 +0300 | [diff] [blame] | 86 |     @property | 
 | 87 |     def api(self): | 
| Vladimir Jigulin | 4ad52a8 | 2018-08-12 05:51:30 +0400 | [diff] [blame] | 88 |         """ | 
 | 89 |             :rtype: cluster.K8sCluster | 
 | 90 |         """ | 
 | 91 |         if self._api is None: | 
 | 92 |             self._api_init() | 
 | 93 |         return self._api | 
| Artem Panchenko | 0594cd7 | 2017-06-12 13:25:26 +0300 | [diff] [blame] | 94 |  | 
| Vladimir Jigulin | 4ad52a8 | 2018-08-12 05:51:30 +0400 | [diff] [blame] | 95 |     def get_controllers(self): | 
 | 96 |         """ Return list of controllers ssh underlays """ | 
| Vladimir Jigulin | 34dfa94 | 2018-07-23 21:05:48 +0400 | [diff] [blame] | 97 |         return [node for node in self.__config.underlay.ssh if | 
 | 98 |                 ext.UNDERLAY_NODE_ROLES.k8s_controller in node['roles']] | 
 | 99 |  | 
| Vladimir Jigulin | 4ad52a8 | 2018-08-12 05:51:30 +0400 | [diff] [blame] | 100 |     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 Ryzhenkin | 66d3937 | 2017-09-28 19:25:48 +0400 | [diff] [blame] | 107 |     @property | 
| Vladimir Jigulin | 4ad52a8 | 2018-08-12 05:51:30 +0400 | [diff] [blame] | 108 |     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 Ryzhenkin | 66d3937 | 2017-09-28 19:25:48 +0400 | [diff] [blame] | 114 |  | 
| Vladimir Jigulin | 4ad52a8 | 2018-08-12 05:51:30 +0400 | [diff] [blame] | 115 |     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 Jigulin | ee1faa5 | 2018-06-25 13:00:51 +0400 | [diff] [blame] | 118 |         return self.__underlay.check_call( | 
| Vladimir Jigulin | 4ad52a8 | 2018-08-12 05:51:30 +0400 | [diff] [blame] | 119 |             cmd=cmd, node_name=self.controller_name, **kwargs) | 
| Artem Panchenko | 501e67e | 2017-06-14 14:59:18 +0300 | [diff] [blame] | 120 |  | 
| Vladimir Jigulin | 4ad52a8 | 2018-08-12 05:51:30 +0400 | [diff] [blame] | 121 |     def get_keepalived_vip(self): | 
| Vladimir Jigulin | 62bcf46 | 2018-05-28 18:17:01 +0400 | [diff] [blame] | 122 |         """ | 
| Vladimir Jigulin | 4ad52a8 | 2018-08-12 05:51:30 +0400 | [diff] [blame] | 123 |         Return k8s VIP IP address | 
| Vladimir Jigulin | 62bcf46 | 2018-05-28 18:17:01 +0400 | [diff] [blame] | 124 |  | 
| Vladimir Jigulin | 4ad52a8 | 2018-08-12 05:51:30 +0400 | [diff] [blame] | 125 |         :return: str, IP address | 
| Vladimir Jigulin | 62bcf46 | 2018-05-28 18:17:01 +0400 | [diff] [blame] | 126 |         """ | 
| Vladimir Jigulin | 4ad52a8 | 2018-08-12 05:51:30 +0400 | [diff] [blame] | 127 |         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 Jigulin | 62bcf46 | 2018-05-28 18:17:01 +0400 | [diff] [blame] | 131 |  | 
| Vladimir Jigulin | 4ad52a8 | 2018-08-12 05:51:30 +0400 | [diff] [blame] | 132 |     def run_sample_deployment(self, name, **kwargs): | 
 | 133 |         return K8SSampleDeployment(self, name, **kwargs) | 
| Victor Ryzhenkin | 3ffa2b4 | 2017-10-05 16:38:44 +0400 | [diff] [blame] | 134 |  | 
| Vladimir Jigulin | 4ad52a8 | 2018-08-12 05:51:30 +0400 | [diff] [blame] | 135 |     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 Ryzhenkin | 3ffa2b4 | 2017-10-05 16:38:44 +0400 | [diff] [blame] | 141 |         """ | 
| Vladimir Jigulin | 4ad52a8 | 2018-08-12 05:51:30 +0400 | [diff] [blame] | 142 |         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 Ryzhenkin | 3ffa2b4 | 2017-10-05 16:38:44 +0400 | [diff] [blame] | 148 |  | 
| Vladimir Jigulin | 4ad52a8 | 2018-08-12 05:51:30 +0400 | [diff] [blame] | 149 |     def update_k8s_version(self, tag): | 
| Vladimir Jigulin | 62bcf46 | 2018-05-28 18:17:01 +0400 | [diff] [blame] | 150 |         """ | 
 | 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 Jigulin | ee1faa5 | 2018-06-25 13:00:51 +0400 | [diff] [blame] | 183 |  | 
| Vladimir Jigulin | 4ad52a8 | 2018-08-12 05:51:30 +0400 | [diff] [blame] | 184 |     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, | 
 | 195 |                raise_on_err=raise_on_err) | 
 | 196 |  | 
 | 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 Jigulin | 0c8dd5a | 2018-08-28 05:08:35 +0400 | [diff] [blame] | 231 |         """ | 
 | 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 Jigulin | 4ad52a8 | 2018-08-12 05:51:30 +0400 | [diff] [blame] | 245 |  | 
| Vladimir Jigulin | 0c8dd5a | 2018-08-28 05:08:35 +0400 | [diff] [blame] | 246 |         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 Jigulin | 4ad52a8 | 2018-08-12 05:51:30 +0400 | [diff] [blame] | 265 |  | 
 | 266 |     def extract_file_to_node(self, system='docker', | 
 | 267 |                              container='virtlet', | 
 | 268 |                              file_path='report.xml', | 
 | 269 |                              out_dir='.', | 
 | 270 |                              **kwargs): | 
| Vladimir Jigulin | ee1faa5 | 2018-06-25 13:00:51 +0400 | [diff] [blame] | 271 |         """ | 
| Vladimir Jigulin | 4ad52a8 | 2018-08-12 05:51:30 +0400 | [diff] [blame] | 272 |         Download file from docker or k8s container to node | 
| Vladimir Jigulin | ee1faa5 | 2018-06-25 13:00:51 +0400 | [diff] [blame] | 273 |  | 
| Vladimir Jigulin | 4ad52a8 | 2018-08-12 05:51:30 +0400 | [diff] [blame] | 274 |         :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 Jigulin | ee1faa5 | 2018-06-25 13:00:51 +0400 | [diff] [blame] | 280 |         """ | 
| Vladimir Jigulin | 0c8dd5a | 2018-08-28 05:08:35 +0400 | [diff] [blame] | 281 |         with self.__underlay.remote(node_name=self.controller_name) as remote: | 
| Vladimir Jigulin | 4ad52a8 | 2018-08-12 05:51:30 +0400 | [diff] [blame] | 282 |             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 Jigulin | a6b018b | 2018-07-18 15:19:01 +0400 | [diff] [blame] | 303 |  | 
| Vladimir Jigulin | 4ad52a8 | 2018-08-12 05:51:30 +0400 | [diff] [blame] | 304 |     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()) | 
| Vladimir Jigulin | a6b018b | 2018-07-18 15:19:01 +0400 | [diff] [blame] | 318 |  | 
| Vladimir Jigulin | 4ad52a8 | 2018-08-12 05:51:30 +0400 | [diff] [blame] | 319 |     def combine_xunit(self, path, output): | 
 | 320 |         """ | 
 | 321 |         Function to combine multiple xmls with test results to | 
 | 322 |         one. | 
| Vladimir Jigulin | 34dfa94 | 2018-07-23 21:05:48 +0400 | [diff] [blame] | 323 |  | 
| Vladimir Jigulin | 4ad52a8 | 2018-08-12 05:51:30 +0400 | [diff] [blame] | 324 |         :param path: Path where xmls to combine located | 
 | 325 |         :param output: Path to xml file where output will stored | 
 | 326 |         :return: | 
 | 327 |         """ | 
 | 328 |         with self.__underlay.remote(node_name=self.controller_name) as r: | 
 | 329 |             cmd = ("apt-get install python-setuptools -y; " | 
 | 330 |                    "pip install " | 
 | 331 |                    "https://github.com/mogaika/xunitmerge/archive/master.zip") | 
 | 332 |             LOG.debug('Installing xunitmerge') | 
 | 333 |             r.check_call(cmd, raise_on_err=False) | 
 | 334 |             LOG.debug('Merging xunit') | 
 | 335 |             cmd = ("cd {0}; arg = ''; " | 
 | 336 |                    "for i in $(ls | grep xml); " | 
 | 337 |                    "do arg=\"$arg $i\"; done && " | 
 | 338 |                    "xunitmerge $arg {1}".format(path, output)) | 
 | 339 |             r.check_call(cmd, raise_on_err=False) | 
| Vladimir Jigulin | 34dfa94 | 2018-07-23 21:05:48 +0400 | [diff] [blame] | 340 |  | 
| Vladimir Jigulin | 4ad52a8 | 2018-08-12 05:51:30 +0400 | [diff] [blame] | 341 |     def manage_cncf_archive(self): | 
 | 342 |         """ | 
| Vladimir Jigulin | 0c8dd5a | 2018-08-28 05:08:35 +0400 | [diff] [blame] | 343 |         Function to untar archive, move files that we are needs to the | 
 | 344 |         home folder, prepare it to downloading. | 
 | 345 |         Will generate files on controller node: | 
 | 346 |             e2e.log, junit_01.xml, cncf_results.tar.gz, version.txt | 
| Vladimir Jigulin | 4ad52a8 | 2018-08-12 05:51:30 +0400 | [diff] [blame] | 347 |         :return: | 
 | 348 |         """ | 
 | 349 |  | 
| Vladimir Jigulin | 0c8dd5a | 2018-08-28 05:08:35 +0400 | [diff] [blame] | 350 |         cmd =\ | 
 | 351 |             "rm -rf cncf_results.tar.gz result && " \ | 
 | 352 |             "mkdir result && " \ | 
 | 353 |             "mv *_sonobuoy_*.tar.gz cncf_results.tar.gz && " \ | 
 | 354 |             "tar -C result -xzf cncf_results.tar.gz && " \ | 
 | 355 |             "mv result/plugins/e2e/results/e2e.log . ; " \ | 
 | 356 |             "mv result/plugins/e2e/results/junit_01.xml . ; " \ | 
 | 357 |             "kubectl version > version.txt" | 
| Vladimir Jigulin | 4ad52a8 | 2018-08-12 05:51:30 +0400 | [diff] [blame] | 358 |  | 
| Vladimir Jigulin | 0c8dd5a | 2018-08-28 05:08:35 +0400 | [diff] [blame] | 359 |         self.controller_check_call(cmd, raise_on_err=False) | 
| Vladimir Jigulin | 4ad52a8 | 2018-08-12 05:51:30 +0400 | [diff] [blame] | 360 |  | 
 | 361 |     @retry(300, exception=DevopsCalledProcessError) | 
 | 362 |     def nslookup(self, host, src): | 
 | 363 |         """ Run nslookup on controller and return result """ | 
 | 364 |         return self.controller_check_call("nslookup {0} {1}".format(host, src)) | 
 | 365 |  | 
 | 366 |     @retry(300, exception=DevopsCalledProcessError) | 
 | 367 |     def curl(self, url): | 
 | 368 |         """ | 
 | 369 |         Run curl on controller and return stdout | 
 | 370 |  | 
 | 371 |         :param url: url to curl | 
 | 372 |         :return: response string | 
 | 373 |         """ | 
 | 374 |         result = self.controller_check_call("curl -s -S \"{}\"".format(url)) | 
 | 375 |         LOG.debug("curl \"{0}\" result: {1}".format(url, result['stdout'])) | 
 | 376 |         return result['stdout'] | 
| Vladimir Jigulin | 34dfa94 | 2018-07-23 21:05:48 +0400 | [diff] [blame] | 377 |  | 
| Vladimir Jigulin | a6b018b | 2018-07-18 15:19:01 +0400 | [diff] [blame] | 378 |  | 
| Vladimir Jigulin | 4ad52a8 | 2018-08-12 05:51:30 +0400 | [diff] [blame] | 379 | class K8SKubectlCli(object): | 
 | 380 |     """ Contain kubectl cli commands and api wrappers""" | 
 | 381 |     def __init__(self, manager): | 
 | 382 |         self._manager = manager | 
| Vladimir Jigulin | a6b018b | 2018-07-18 15:19:01 +0400 | [diff] [blame] | 383 |  | 
| Vladimir Jigulin | 4ad52a8 | 2018-08-12 05:51:30 +0400 | [diff] [blame] | 384 |     def cli_run(self, namespace, name, image, port, replicas=1): | 
 | 385 |         cmd = "kubectl -n {0} run {1} --image={2} --port={3} --replicas={4}".\ | 
 | 386 |             format(namespace, name, image, port, replicas) | 
 | 387 |         return self._manager.controller_check_call(cmd) | 
| Vladimir Jigulin | a6b018b | 2018-07-18 15:19:01 +0400 | [diff] [blame] | 388 |  | 
| Vladimir Jigulin | 4ad52a8 | 2018-08-12 05:51:30 +0400 | [diff] [blame] | 389 |     def run(self, namespace, name, image, port, replicas=1): | 
 | 390 |         self.cli_run(namespace, name, image, port, replicas) | 
 | 391 |         return self._manager.api.deployments.get( | 
 | 392 |             namespace=namespace, name=name) | 
| Vladimir Jigulin | a6b018b | 2018-07-18 15:19:01 +0400 | [diff] [blame] | 393 |  | 
| Vladimir Jigulin | 4ad52a8 | 2018-08-12 05:51:30 +0400 | [diff] [blame] | 394 |     def cli_expose(self, namespace, resource_type, resource_name, | 
 | 395 |                    service_name=None, port='', service_type='ClusterIP'): | 
 | 396 |         cmd = "kubectl -n {0} expose {1} {2} --port={3} --type={4}".format( | 
 | 397 |             namespace, resource_type, resource_name, port, service_type) | 
 | 398 |         if service_name is not None: | 
 | 399 |             cmd += " --name={}".format(service_name) | 
 | 400 |         return self._manager.controller_check_call(cmd) | 
| Vladimir Jigulin | a6b018b | 2018-07-18 15:19:01 +0400 | [diff] [blame] | 401 |  | 
| Vladimir Jigulin | 4ad52a8 | 2018-08-12 05:51:30 +0400 | [diff] [blame] | 402 |     def expose(self, resource, service_name=None, | 
 | 403 |                port='', service_type='ClusterIP'): | 
 | 404 |         self.cli_expose(resource.namespace, resource.resource_type, | 
 | 405 |                         resource.name, service_name=service_name, | 
 | 406 |                         port=port, service_type=service_type) | 
 | 407 |         return self._manager.api.services.get( | 
 | 408 |             namespace=resource.namespace, name=service_name or resource.name) | 
 | 409 |  | 
 | 410 |     def cli_exec(self, namespace, pod_name, cmd, container=''): | 
 | 411 |         kubectl_cmd = "kubectl -n {0} exec --container={1} {2} -- {3}".format( | 
 | 412 |             namespace, container, pod_name, cmd) | 
 | 413 |         return self._manager.controller_check_call(kubectl_cmd) | 
 | 414 |  | 
 | 415 |     # def exec(...), except exec is statement in python | 
 | 416 |     def execute(self, pod, cmd, container=''): | 
 | 417 |         return self.cli_exec(pod.namespace, pod.name, cmd, container=container) | 
 | 418 |  | 
 | 419 |     def cli_annotate(self, namespace, resource_type, resource_name, | 
 | 420 |                      annotations, overwrite=False): | 
 | 421 |         cmd = "kubectl -n {0} annotate {1} {2} {3}".format( | 
 | 422 |             namespace, resource_type, resource_name, annotations) | 
 | 423 |         if overwrite: | 
 | 424 |             cmd += " --overwrite" | 
 | 425 |         return self._manager.controller_check_call(cmd) | 
 | 426 |  | 
 | 427 |     def annotate(self, resource, annotations, overwrite=False): | 
 | 428 |         return self.cli_annotate(resource.namespace, resource.resource_type, | 
 | 429 |                                  resource.name, annotations, | 
 | 430 |                                  overwrite=overwrite) | 
 | 431 |  | 
 | 432 |  | 
 | 433 | class K8SVirtlet(object): | 
 | 434 |     """ Contain virtlet-related methods""" | 
 | 435 |     def __init__(self, manager, namespace='kube-system'): | 
 | 436 |         self._manager = manager | 
 | 437 |         self._namespace = namespace | 
 | 438 |  | 
 | 439 |     def get_virtlet_node_pod(self, node_name): | 
 | 440 |         for pod in self._manager.api.pods.list( | 
 | 441 |                 namespace=self._namespace, name_prefix='virtlet-'): | 
 | 442 |             if pod.read().spec.node_name == node_name: | 
 | 443 |                 return pod | 
 | 444 |         return None | 
 | 445 |  | 
 | 446 |     def get_pod_dom_uuid(self, pod): | 
 | 447 |         uuid_name_map = self.virtlet_execute( | 
 | 448 |             pod.read().spec.node_name, 'virsh list --uuid --name')['stdout'] | 
| Vladimir Jigulin | 4ad52a8 | 2018-08-12 05:51:30 +0400 | [diff] [blame] | 449 |         for line in uuid_name_map: | 
 | 450 |             if line.rstrip().endswith("-{}".format(pod.name)): | 
 | 451 |                 return line.split(" ")[0] | 
 | 452 |         raise Exception("Cannot detect uuid for pod {}".format(pod.name)) | 
 | 453 |  | 
 | 454 |     def virsh_domstats(self, pod): | 
 | 455 |         """ get dict of vm stats """ | 
 | 456 |         uuid = self.get_pod_dom_uuid(pod) | 
 | 457 |         result = self.virtlet_execute( | 
 | 458 |             pod.read().spec.node_name, 'virsh domstats {}'.format(uuid)) | 
 | 459 |         stats = dict() | 
 | 460 |         for line in result['stdout']: | 
 | 461 |             if '=' in line: | 
 | 462 |                 vals = line.strip().split('=') | 
 | 463 |                 stats[vals[0]] = vals[1] | 
 | 464 |         return stats | 
 | 465 |  | 
 | 466 |     def virtlet_execute(self, node_name, cmd, container='libvirt'): | 
 | 467 |         """ execute command inside virtlet container """ | 
 | 468 |         pod = self.get_virtlet_node_pod(node_name) | 
 | 469 |         return self._manager.kubectl.execute(pod, cmd, container) | 
 | 470 |  | 
 | 471 |  | 
 | 472 | class K8SSampleDeployment(object): | 
 | 473 |     """ Wrapper for deployment run=>expose=>check frequent combination """ | 
 | 474 |     def __init__(self, manager, name, | 
 | 475 |                  namespace=None, | 
 | 476 |                  image='gcr.io/google-samples/node-hello:1.0', | 
 | 477 |                  port=8080, | 
 | 478 |                  replicas=2): | 
 | 479 |         namespace = namespace or manager.api.default_namespace | 
 | 480 |  | 
 | 481 |         self._manager = manager | 
 | 482 |         self._port = port | 
 | 483 |         self._deployment = \ | 
 | 484 |             manager.kubectl.run(namespace, name, | 
 | 485 |                                 image=image, port=port, replicas=replicas) | 
 | 486 |         self._index = 1  # used to generate svc name | 
 | 487 |         self._svc = None  # hold last created svc | 
 | 488 |  | 
 | 489 |     def wait_ready(self, timeout=300, interval=5): | 
 | 490 |         self._deployment.wait_ready(timeout=timeout, interval=interval) | 
 | 491 |         return self | 
 | 492 |  | 
 | 493 |     def svc(self): | 
 | 494 |         """ Return the last exposed service""" | 
 | 495 |         return self._svc | 
 | 496 |  | 
 | 497 |     def expose(self, service_type='ClusterIP'): | 
 | 498 |         service_name = "{0}-s{1}".format(self._deployment.name, self._index) | 
| Vladimir Jigulin | 9068915 | 2018-09-26 15:38:19 +0400 | [diff] [blame^] | 499 |         self._index += 1 | 
| Vladimir Jigulin | 4ad52a8 | 2018-08-12 05:51:30 +0400 | [diff] [blame] | 500 |         self._svc = self._manager.kubectl.expose( | 
 | 501 |             self._deployment, port=self._port, | 
 | 502 |             service_name=service_name, service_type=service_type) | 
 | 503 |         return self._svc | 
 | 504 |  | 
 | 505 |     def curl(self, svc=None, external=False): | 
 | 506 |         if svc is None: | 
 | 507 |             svc = self.svc() | 
 | 508 |         url = "http://{0}:{1}".format(svc.get_ip(external), self._port) | 
| Vladimir Jigulin | a6b018b | 2018-07-18 15:19:01 +0400 | [diff] [blame] | 509 |         if external: | 
 | 510 |             return requests.get(url).text | 
 | 511 |         else: | 
| Vladimir Jigulin | 4ad52a8 | 2018-08-12 05:51:30 +0400 | [diff] [blame] | 512 |             return self._manager.curl(url) | 
| Vladimir Jigulin | a6b018b | 2018-07-18 15:19:01 +0400 | [diff] [blame] | 513 |  | 
| Vladimir Jigulin | 4ad52a8 | 2018-08-12 05:51:30 +0400 | [diff] [blame] | 514 |     def is_service_available(self, svc=None, external=False): | 
 | 515 |         return "Hello Kubernetes!" in self.curl(svc, external=external) | 
| Vladimir Jigulin | 9068915 | 2018-09-26 15:38:19 +0400 | [diff] [blame^] | 516 |  | 
 | 517 |     def delete(self): | 
 | 518 |         for svc in self._manager.api.services.list_all( | 
 | 519 |                 name_prefix="{}-s".format(self._deployment.name)): | 
 | 520 |             svc.delete() | 
 | 521 |         self._deployment.delete() |