Avoid running VNF testing if deployment failed
[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 logging
15 import time
16 import re
17 import yaml
18
19 from kubernetes import client
20 from kubernetes import config
21 from kubernetes import watch
22 import pkg_resources
23
24 from xtesting.core import testcase
25
26
27 class Vims(testcase.TestCase):  # pylint: disable=too-many-instance-attributes
28     """Deploy and test Clearwater vIMS using Kubernetes
29
30     It leverage on the Python kubernetes client to apply operation proposed by
31     clearwater-docker.
32
33     See https://github.com/Metaswitch/clearwater-docker for more details
34     """
35     watch_timeout = 1200
36     metadata_name = "env-vars"
37     test_image_name = "ollivier/clearwater-live-test:hunter"
38     test_container_name = "live-test"
39
40     __logger = logging.getLogger(__name__)
41
42     deployment_list = [
43         "astaire", "bono", "cassandra", "chronos", "ellis", "etcd", "homer",
44         "homestead", "homestead-prov", "ralf", "sprout"]
45
46     def __init__(self, **kwargs):
47         super(Vims, self).__init__(**kwargs)
48         config.load_kube_config()
49         self.corev1 = client.CoreV1Api()
50         self.appsv1 = client.AppsV1Api()
51         self.output_log_name = 'functest-kubernetes.log'
52         self.output_debug_log_name = 'functest-kubernetes.debug.log'
53         self.namespace = ""
54         self.zone = ""
55
56     def deploy_vnf(self):
57         """Deploy vIMS as proposed by clearwater-docker
58
59         It leverages on unofficial Clearwater dockers as proposed in the
60         documentation.
61
62         See https://github.com/Metaswitch/clearwater-docker for more details
63         """
64         api_response = self.corev1.create_namespace(
65             client.V1Namespace(metadata=client.V1ObjectMeta(
66                 generate_name="ims-")))
67         self.namespace = api_response.metadata.name
68         self.__logger.debug("create_namespace: %s", api_response)
69         metadata = client.V1ObjectMeta(
70             name=self.metadata_name, namespace=self.namespace)
71         self.zone = '{}.svc.cluster.local'.format(self.namespace)
72         body = client.V1ConfigMap(
73             metadata=metadata,
74             data={"ADDITIONAL_SHARED_CONFIG": "", "ZONE": self.zone})
75         api_response = self.corev1.create_namespaced_config_map(
76             self.namespace, body=body)
77         self.__logger.debug("create_namespaced_config_map: %s", api_response)
78         for deployment in self.deployment_list:
79             with open(pkg_resources.resource_filename(
80                     'functest_kubernetes',
81                     'ims/{}-depl.yaml'.format(deployment))) as yfile:
82                 body = yaml.safe_load(yfile)
83                 resp = self.appsv1.create_namespaced_deployment(
84                     body=body, namespace=self.namespace)
85                 self.__logger.info("Deployment %s created", resp.metadata.name)
86                 self.__logger.debug(
87                     "create_namespaced_deployment: %s", api_response)
88         for service in self.deployment_list:
89             with open(pkg_resources.resource_filename(
90                     'functest_kubernetes',
91                     'ims/{}-svc.yaml'.format(service))) as yfile:
92                 body = yaml.safe_load(yfile)
93                 resp = self.corev1.create_namespaced_service(
94                     body=body, namespace=self.namespace)
95                 self.__logger.info("Service %s created", resp.metadata.name)
96                 self.__logger.debug(
97                     "create_namespaced_service: %s", api_response)
98         status = self.deployment_list.copy()
99         watch_deployment = watch.Watch()
100         for event in watch_deployment.stream(
101                 func=self.appsv1.list_namespaced_deployment,
102                 namespace=self.namespace, timeout_seconds=self.watch_timeout):
103             self.__logger.debug(event)
104             if event["object"].status.ready_replicas == 1:
105                 if event['object'].metadata.name in status:
106                     status.remove(event['object'].metadata.name)
107                     self.__logger.info(
108                         "%s started in %0.2f sec",
109                         event['object'].metadata.name,
110                         time.time()-self.start_time)
111             if not status:
112                 watch_deployment.stop()
113         if not status:
114             self.result = 1/2 * 100
115             return True
116         self.__logger.error("Cannot deploy vIMS")
117         return False
118
119     def test_vnf(self):
120         """Test vIMS as proposed by clearwater-live-test
121
122         It leverages on an unofficial Clearwater docker to allow testing from
123         the Kubernetes cluster.
124
125         See https://github.com/Metaswitch/clearwater-live-test for more details
126         """
127         time.sleep(120)
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             if self.deploy_vnf():
175                 self.test_vnf()
176         except client.rest.ApiException:
177             self.__logger.exception("Cannot deploy and test vIms")
178         self.stop_time = time.time()
179
180     def clean(self):
181         try:
182             api_response = self.corev1.delete_namespaced_config_map(
183                 name=self.metadata_name, namespace=self.namespace)
184             self.__logger.debug(
185                 "delete_namespaced_config_map: %s", api_response)
186         except client.rest.ApiException:
187             pass
188         try:
189             api_response = self.corev1.delete_namespaced_pod(
190                 name=self.test_container_name, namespace=self.namespace)
191             self.__logger.debug("delete_namespaced_pod: %s", api_response)
192         except client.rest.ApiException:
193             pass
194         for deployment in self.deployment_list:
195             try:
196                 api_response = self.appsv1.delete_namespaced_deployment(
197                     name=deployment, namespace=self.namespace)
198                 self.__logger.debug(
199                     "delete_namespaced_deployment: %s", api_response)
200             except client.rest.ApiException:
201                 pass
202             try:
203                 api_response = self.corev1.delete_namespaced_service(
204                     name=deployment, namespace=self.namespace)
205                 self.__logger.debug(
206                     "delete_namespaced_service: %s", api_response)
207             except client.rest.ApiException:
208                 pass
209         try:
210             api_response = self.corev1.delete_namespace(self.namespace)
211             self.__logger.debug("delete_namespace: %s", self.namespace)
212         except client.rest.ApiException:
213             pass