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