Improve Ims coding
[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 import logging
13 import time
14 import re
15 import yaml
16
17 from kubernetes import client
18 from kubernetes import config
19 from kubernetes import watch
20 import pkg_resources
21
22 from xtesting.core import testcase
23
24
25 class Vims(testcase.TestCase):
26     """Deploy and test Clearwater vIMS using Kubernetes
27
28     It leverage on the Python kubernetes client to apply operation proposed by
29     clearwater-docker.
30
31     See https://github.com/Metaswitch/clearwater-docker for more details
32     """
33     namespace = 'default'
34     zone = 'default.svc.cluster.local'
35     watch_timeout = 1200
36     metadata_name = "env-vars"
37     test_image_name = "ollivier/clearwater-live-test:latest"
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
54     def deploy_vnf(self):
55         """Deploy vIMS as proposed by clearwater-docker
56
57         It leverages on unofficial Clearwater dockers as proposed in the
58         documentation.
59
60         See https://github.com/Metaswitch/clearwater-docker for more details
61         """
62         metadata = client.V1ObjectMeta(
63             name=self.metadata_name, namespace=self.namespace)
64         body = client.V1ConfigMap(
65             metadata=metadata,
66             data={"ADDITIONAL_SHARED_CONFIG": "", "ZONE": self.zone})
67         api_response = self.corev1.create_namespaced_config_map(
68             self.namespace, body=body)
69         self.__logger.debug("create_namespaced_config_map: %s", api_response)
70         for deployment in self.deployment_list:
71             with open(pkg_resources.resource_filename(
72                     'functest_kubernetes',
73                     'ims/{}-depl.yaml'.format(deployment))) as yfile:
74                 body = yaml.safe_load(yfile)
75                 resp = self.appsv1.create_namespaced_deployment(
76                     body=body, namespace="default")
77                 self.__logger.info("Deployment %s created", resp.metadata.name)
78                 self.__logger.debug(
79                     "create_namespaced_deployment: %s", api_response)
80         for service in self.deployment_list:
81             with open(pkg_resources.resource_filename(
82                     'functest_kubernetes',
83                     'ims/{}-svc.yaml'.format(service))) as yfile:
84                 body = yaml.safe_load(yfile)
85                 resp = self.corev1.create_namespaced_service(
86                     body=body, namespace="default")
87                 self.__logger.info("Service %s created", resp.metadata.name)
88                 self.__logger.debug(
89                     "create_namespaced_service: %s", api_response)
90         status = self.deployment_list.copy()
91         watch_deployment = watch.Watch()
92         for event in watch_deployment.stream(
93                 func=self.appsv1.list_namespaced_deployment,
94                 namespace=self.namespace, timeout_seconds=self.watch_timeout):
95             self.__logger.debug(event)
96             if event["object"].status.ready_replicas == 1:
97                 if event['object'].metadata.name in status:
98                     status.remove(event['object'].metadata.name)
99                     self.__logger.info(
100                         "%s started in %0.2f sec",
101                         event['object'].metadata.name,
102                         time.time()-self.start_time)
103             if not status:
104                 watch_deployment.stop()
105         self.result = 1/2 * 100
106
107     def test_vnf(self):
108         """Test vIMS as proposed by clearwater-live-test
109
110         It leverages on an unofficial Clearwater docker to allow testing from
111         the Kubernetes cluster.
112
113         See https://github.com/Metaswitch/clearwater-live-test for more details
114         """
115         container = client.V1Container(
116             name=self.test_container_name, image=self.test_image_name)
117         spec = client.V1PodSpec(containers=[container], restart_policy="Never")
118         metadata = client.V1ObjectMeta(name=self.test_container_name)
119         body = client.V1Pod(metadata=metadata, spec=spec)
120         api_response = self.corev1.create_namespaced_pod(self.namespace, body)
121         watch_deployment = watch.Watch()
122         for event in watch_deployment.stream(
123                 func=self.corev1.list_namespaced_pod,
124                 namespace=self.namespace, timeout_seconds=self.watch_timeout):
125             self.__logger.debug(event)
126             if event["object"].metadata.name == self.test_container_name:
127                 if (event["object"].status.phase == 'Succeeded'
128                         or event["object"].status.phase == 'Failed'):
129                     watch_deployment.stop()
130         api_response = self.corev1.read_namespaced_pod_log(
131             name=self.test_container_name, namespace=self.namespace)
132         self.__logger.info(api_response)
133         vims_test_result = {}
134         try:
135             grp = re.search(
136                 r'^(\d+) failures out of (\d+) tests run.*\n'
137                 r'(\d+) tests skipped$', api_response,
138                 re.MULTILINE | re.DOTALL)
139             assert grp
140             vims_test_result["failures"] = int(grp.group(1))
141             vims_test_result["total"] = int(grp.group(2))
142             vims_test_result["skipped"] = int(grp.group(3))
143             vims_test_result['passed'] = (
144                 int(grp.group(2)) - int(grp.group(3)) - int(grp.group(1)))
145             if vims_test_result['total'] - vims_test_result['skipped'] > 0:
146                 vnf_test_rate = vims_test_result['passed'] / (
147                     vims_test_result['total'] - vims_test_result['skipped'])
148             else:
149                 vnf_test_rate = 0
150             self.result += 1/2 * 100 * vnf_test_rate
151         except Exception:  # pylint: disable=broad-except
152             self.__logger.exception("Cannot parse live tests results")
153
154     def run(self, **kwargs):
155         self.start_time = time.time()
156         try:
157             self.deploy_vnf()
158             self.test_vnf()
159         except client.rest.ApiException:
160             self.__logger.exception("Cannot deploy and test vIms")
161         self.stop_time = time.time()
162
163     def clean(self):
164         try:
165             api_response = self.corev1.delete_namespaced_config_map(
166                 name=self.metadata_name, namespace=self.namespace)
167             self.__logger.debug(
168                 "delete_namespaced_config_map: %s", api_response)
169         except client.rest.ApiException:
170             pass
171         try:
172             api_response = self.corev1.delete_namespaced_pod(
173                 name=self.test_container_name, namespace=self.namespace)
174             self.__logger.debug("delete_namespaced_pod: %s", api_response)
175         except client.rest.ApiException:
176             pass
177         for deployment in self.deployment_list:
178             try:
179                 api_response = self.appsv1.delete_namespaced_deployment(
180                     name=deployment, namespace=self.namespace)
181                 self.__logger.debug(
182                     "delete_namespaced_deployment: %s", api_response)
183             except client.rest.ApiException:
184                 pass
185             try:
186                 api_response = self.corev1.delete_namespaced_service(
187                     name=deployment, namespace=self.namespace)
188                 self.__logger.debug(
189                     "delete_namespaced_service: %s", api_response)
190             except client.rest.ApiException:
191                 pass