18e8c0de5a75b374e06609c7c94e1cd9804bba48
[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 = '{}.svc.cluster.local'.format(self.namespace)
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", "test[{}]".format(self.zone),
131                      "PROXY=bono.{}".format(self.zone),
132                      "ELLIS=ellis.{}".format(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 == 'Succeeded'
145                         or event["object"].status.phase == 'Failed'):
146                     watch_deployment.stop()
147         api_response = self.corev1.read_namespaced_pod_log(
148             name=self.test_container_name, namespace=self.namespace)
149         self.__logger.info(api_response)
150         vims_test_result = {}
151         try:
152             grp = re.search(
153                 r'^(\d+) failures out of (\d+) tests run.*\n'
154                 r'(\d+) tests skipped$', api_response,
155                 re.MULTILINE | re.DOTALL)
156             assert grp
157             vims_test_result["failures"] = int(grp.group(1))
158             vims_test_result["total"] = int(grp.group(2))
159             vims_test_result["skipped"] = int(grp.group(3))
160             vims_test_result['passed'] = (
161                 int(grp.group(2)) - int(grp.group(3)) - int(grp.group(1)))
162             if vims_test_result['total'] - vims_test_result['skipped'] > 0:
163                 vnf_test_rate = vims_test_result['passed'] / (
164                     vims_test_result['total'] - vims_test_result['skipped'])
165             else:
166                 vnf_test_rate = 0
167             self.result += 1/2 * 100 * vnf_test_rate
168         except Exception:  # pylint: disable=broad-except
169             self.__logger.exception("Cannot parse live tests results")
170
171     def run(self, **kwargs):
172         self.start_time = time.time()
173         try:
174             self.prepare_vnf()
175             self.deploy_vnf()
176             if self.wait_vnf():
177                 self.test_vnf()
178         except client.rest.ApiException:
179             self.__logger.exception("Cannot deploy and test vIms")
180         self.stop_time = time.time()
181
182     def clean(self):
183         try:
184             api_response = self.corev1.delete_namespaced_pod(
185                 name=self.test_container_name, namespace=self.namespace)
186             self.__logger.debug("delete_namespaced_pod: %s", api_response)
187         except client.rest.ApiException:
188             pass
189         try:
190             api_response = self.corev1.delete_namespaced_config_map(
191                 name=self.metadata_name, namespace=self.namespace)
192             self.__logger.debug(
193                 "delete_namespaced_config_map: %s", api_response)
194         except client.rest.ApiException:
195             pass
196         try:
197             api_response = self.corev1.delete_namespace(self.namespace)
198             self.__logger.debug("delete_namespace: %s", self.namespace)
199         except client.rest.ApiException:
200             pass
201
202
203 class K8sVims(Vims):
204     """Deploy vIMS via kubectl as proposed by clearwater-docker
205
206     It leverages unofficial Clearwater dockers as proposed in the
207     documentation.
208
209     See https://github.com/Metaswitch/clearwater-docker for more details
210     """
211
212     __logger = logging.getLogger(__name__)
213
214     def deploy_vnf(self):
215         """Deploy vIMS via kubectl as proposed by clearwater-docker
216
217         See https://github.com/Metaswitch/clearwater-docker for more details
218         """
219         assert self.namespace
220         for deployment in self.deployment_list:
221             with open(pkg_resources.resource_filename(
222                     'functest_kubernetes',
223                     'ims/{}-depl.yaml'.format(deployment))) 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',
238                     'ims/{}-svc.yaml'.format(service))) 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             "repo.dockerHub={},repo.quay={}".format(dockerhub_repo, 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()