Redirect helm false warnings to logs
[functest-kubernetes.git] / functest_kubernetes / ims / ims.py
1 #!/usr/bin/env python
2
3 # Copyright (c) 2020 Orange and others.
4 #
5 # All rights reserved. This program and the accompanying materials
6 # are made available under the terms of the Apache License, Version 2.0
7 # which accompanies this distribution, and is available at
8 # http://www.apache.org/licenses/LICENSE-2.0
9
10 """Deploy and test Clearwater vIMS using Kubernetes"""
11
12 from __future__ import division
13
14 import abc
15 import logging
16 import time
17 import subprocess
18 import re
19 import yaml
20
21 from kubernetes import client
22 from kubernetes import config
23 from kubernetes import watch
24 import pkg_resources
25
26 from xtesting.core import testcase
27
28
29 class Vims(testcase.TestCase):  # pylint: disable=too-many-instance-attributes
30     """Deploy and test Clearwater vIMS using Kubernetes
31
32     It leverage on the Python kubernetes client to apply operation proposed by
33     clearwater-docker.
34
35     See https://github.com/Metaswitch/clearwater-docker for more details
36     """
37     watch_timeout = 1800
38     metadata_name = "env-vars"
39     test_image_name = "ollivier/clearwater-live-test:hunter"
40     test_container_name = "live-test"
41
42     __logger = logging.getLogger(__name__)
43
44     deployment_list = [
45         "astaire", "bono", "cassandra", "chronos", "ellis", "etcd", "homer",
46         "homestead", "homestead-prov", "ralf", "sprout"]
47
48     def __init__(self, **kwargs):
49         super(Vims, self).__init__(**kwargs)
50         config.load_kube_config()
51         self.corev1 = client.CoreV1Api()
52         self.appsv1 = client.AppsV1Api()
53         self.output_log_name = 'functest-kubernetes.log'
54         self.output_debug_log_name = 'functest-kubernetes.debug.log'
55         self.namespace = ""
56         self.zone = ""
57
58     def prepare_vnf(self):
59         """Prepare vIMS as proposed by clearwater-live-test
60
61         It creates a dedicated namespace and the configmap needed.
62
63         See https://github.com/Metaswitch/clearwater-live-test for more details
64         """
65         api_response = self.corev1.create_namespace(
66             client.V1Namespace(metadata=client.V1ObjectMeta(
67                 generate_name="ims-")))
68         self.namespace = api_response.metadata.name
69         self.__logger.debug("create_namespace: %s", api_response)
70         self.zone = '{}.svc.cluster.local'.format(self.namespace)
71         metadata = client.V1ObjectMeta(
72             name=self.metadata_name, namespace=self.namespace)
73         body = client.V1ConfigMap(
74             metadata=metadata,
75             data={"ADDITIONAL_SHARED_CONFIG": "", "ZONE": self.zone})
76         api_response = self.corev1.create_namespaced_config_map(
77             self.namespace, body=body)
78         self.__logger.debug("create_namespaced_config_map: %s", api_response)
79
80     @abc.abstractmethod
81     def deploy_vnf(self):
82         """Deploy vIMS as proposed by clearwater-docker
83
84         It must be overriden on purpose.
85
86         See https://github.com/Metaswitch/clearwater-docker for more details
87         """
88
89     def wait_vnf(self):
90         """Wait vIMS is up and running"""
91         assert self.namespace
92         status = self.deployment_list.copy()
93         watch_deployment = watch.Watch()
94         for event in watch_deployment.stream(
95                 func=self.appsv1.list_namespaced_deployment,
96                 namespace=self.namespace, timeout_seconds=self.watch_timeout):
97             self.__logger.debug(event)
98             if event["object"].status.ready_replicas == 1:
99                 if event['object'].metadata.name in status:
100                     status.remove(event['object'].metadata.name)
101                     self.__logger.info(
102                         "%s started in %0.2f sec",
103                         event['object'].metadata.name,
104                         time.time()-self.start_time)
105             if not status:
106                 watch_deployment.stop()
107         if not status:
108             self.result = 1/2 * 100
109             return True
110         self.__logger.error("Cannot deploy vIMS")
111         return False
112
113     def test_vnf(self):
114         """Test vIMS as proposed by clearwater-live-test
115
116         It leverages an unofficial Clearwater docker to allow testing from
117         the Kubernetes cluster.
118
119         See https://github.com/Metaswitch/clearwater-live-test for more details
120         """
121         time.sleep(120)
122         assert self.namespace
123         assert self.zone
124         container = client.V1Container(
125             name=self.test_container_name, image=self.test_image_name,
126             command=["rake", "test[{}]".format(self.zone),
127                      "PROXY=bono.{}".format(self.zone),
128                      "ELLIS=ellis.{}".format(self.zone),
129                      "SIGNUP_CODE=secret", "--trace"])
130         spec = client.V1PodSpec(containers=[container], restart_policy="Never")
131         metadata = client.V1ObjectMeta(name=self.test_container_name)
132         body = client.V1Pod(metadata=metadata, spec=spec)
133         api_response = self.corev1.create_namespaced_pod(self.namespace, body)
134         watch_deployment = watch.Watch()
135         for event in watch_deployment.stream(
136                 func=self.corev1.list_namespaced_pod,
137                 namespace=self.namespace, timeout_seconds=self.watch_timeout):
138             self.__logger.debug(event)
139             if event["object"].metadata.name == self.test_container_name:
140                 if (event["object"].status.phase == 'Succeeded' or
141                         event["object"].status.phase == 'Failed'):
142                     watch_deployment.stop()
143         api_response = self.corev1.read_namespaced_pod_log(
144             name=self.test_container_name, namespace=self.namespace)
145         self.__logger.info(api_response)
146         vims_test_result = {}
147         try:
148             grp = re.search(
149                 r'^(\d+) failures out of (\d+) tests run.*\n'
150                 r'(\d+) tests skipped$', api_response,
151                 re.MULTILINE | re.DOTALL)
152             assert grp
153             vims_test_result["failures"] = int(grp.group(1))
154             vims_test_result["total"] = int(grp.group(2))
155             vims_test_result["skipped"] = int(grp.group(3))
156             vims_test_result['passed'] = (
157                 int(grp.group(2)) - int(grp.group(3)) - int(grp.group(1)))
158             if vims_test_result['total'] - vims_test_result['skipped'] > 0:
159                 vnf_test_rate = vims_test_result['passed'] / (
160                     vims_test_result['total'] - vims_test_result['skipped'])
161             else:
162                 vnf_test_rate = 0
163             self.result += 1/2 * 100 * vnf_test_rate
164         except Exception:  # pylint: disable=broad-except
165             self.__logger.exception("Cannot parse live tests results")
166
167     def run(self, **kwargs):
168         self.start_time = time.time()
169         try:
170             self.prepare_vnf()
171             self.deploy_vnf()
172             if self.wait_vnf():
173                 self.test_vnf()
174         except client.rest.ApiException:
175             self.__logger.exception("Cannot deploy and test vIms")
176         self.stop_time = time.time()
177
178     def clean(self):
179         try:
180             api_response = self.corev1.delete_namespaced_pod(
181                 name=self.test_container_name, namespace=self.namespace)
182             self.__logger.debug("delete_namespaced_pod: %s", api_response)
183         except client.rest.ApiException:
184             pass
185         try:
186             api_response = self.corev1.delete_namespaced_config_map(
187                 name=self.metadata_name, namespace=self.namespace)
188             self.__logger.debug(
189                 "delete_namespaced_config_map: %s", api_response)
190         except client.rest.ApiException:
191             pass
192         try:
193             api_response = self.corev1.delete_namespace(self.namespace)
194             self.__logger.debug("delete_namespace: %s", self.namespace)
195         except client.rest.ApiException:
196             pass
197
198
199 class K8sVims(Vims):
200     """Deploy vIMS via kubectl as proposed by clearwater-docker
201
202     It leverages unofficial Clearwater dockers as proposed in the
203     documentation.
204
205     See https://github.com/Metaswitch/clearwater-docker for more details
206     """
207
208     __logger = logging.getLogger(__name__)
209
210     def deploy_vnf(self):
211         """Deploy vIMS via kubectl as proposed by clearwater-docker
212
213         See https://github.com/Metaswitch/clearwater-docker for more details
214         """
215         assert self.namespace
216         for deployment in self.deployment_list:
217             with open(pkg_resources.resource_filename(
218                     'functest_kubernetes',
219                     'ims/{}-depl.yaml'.format(deployment))) as yfile:
220                 body = yaml.safe_load(yfile)
221                 resp = self.appsv1.create_namespaced_deployment(
222                     body=body, namespace=self.namespace)
223                 self.__logger.info("Deployment %s created", resp.metadata.name)
224                 self.__logger.debug(
225                     "create_namespaced_deployment: %s", resp)
226         for service in self.deployment_list:
227             with open(pkg_resources.resource_filename(
228                     'functest_kubernetes',
229                     'ims/{}-svc.yaml'.format(service))) as yfile:
230                 body = yaml.safe_load(yfile)
231                 resp = self.corev1.create_namespaced_service(
232                     body=body, namespace=self.namespace)
233                 self.__logger.info("Service %s created", resp.metadata.name)
234                 self.__logger.debug(
235                     "create_namespaced_service: %s", resp)
236
237     def clean(self):
238         for deployment in self.deployment_list:
239             try:
240                 api_response = self.appsv1.delete_namespaced_deployment(
241                     name=deployment, namespace=self.namespace)
242                 self.__logger.debug(
243                     "delete_namespaced_deployment: %s", api_response)
244             except client.rest.ApiException:
245                 pass
246             try:
247                 api_response = self.corev1.delete_namespaced_service(
248                     name=deployment, namespace=self.namespace)
249                 self.__logger.debug(
250                     "delete_namespaced_service: %s", api_response)
251             except client.rest.ApiException:
252                 pass
253         super(K8sVims, self).clean()
254
255
256 class HelmVims(Vims):
257     """Deploy vIMS via Helm as proposed by clearwater-docker
258
259     It leverages unofficial Clearwater dockers as proposed in the
260     documentation.
261
262     See https://github.com/Metaswitch/clearwater-docker for more details
263     """
264
265     __logger = logging.getLogger(__name__)
266
267     def deploy_vnf(self):
268         """Deploy vIMS via Helm as proposed by clearwater-docker
269
270         See https://github.com/Metaswitch/clearwater-docker for more details
271         """
272         cmd = [
273             "helm", "install", "clearwater",
274             pkg_resources.resource_filename("functest_kubernetes", "ims/helm"),
275             "-n", self.namespace]
276         output = subprocess.check_output(cmd, stderr=subprocess.STDOUT)
277         self.__logger.debug(output.decode("utf-8"))
278
279     def clean(self):
280         cmd = ["helm", "uninstall", "clearwater", "-n", self.namespace]
281         output = subprocess.check_output(cmd, stderr=subprocess.STDOUT)
282         self.__logger.debug(output.decode("utf-8"))
283         super(HelmVims, self).clean()