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