5eb5084cb120b07343c09ce516f0806a25ac01d9
[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):
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     namespace = 'default'
36     zone = 'default.svc.cluster.local'
37     watch_timeout = 1200
38     metadata_name = "env-vars"
39     test_image_name = "ollivier/clearwater-live-test:latest"
40     test_container_name = "live-test"
41
42     __logger = logging.getLogger(__name__)
43
44     deployment_list = [
45         "astaire", "bono", "cassandra", "chronos", "ellis", "etcd", "homer",
46         "homestead", "homestead-prov", "ralf", "sprout"]
47
48     def __init__(self, **kwargs):
49         super(Vims, self).__init__(**kwargs)
50         config.load_kube_config()
51         self.corev1 = client.CoreV1Api()
52         self.appsv1 = client.AppsV1Api()
53         self.output_log_name = 'functest-kubernetes.log'
54         self.output_debug_log_name = 'functest-kubernetes.debug.log'
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         metadata = client.V1ObjectMeta(
65             name=self.metadata_name, namespace=self.namespace)
66         body = client.V1ConfigMap(
67             metadata=metadata,
68             data={"ADDITIONAL_SHARED_CONFIG": "", "ZONE": self.zone})
69         api_response = self.corev1.create_namespaced_config_map(
70             self.namespace, body=body)
71         self.__logger.debug("create_namespaced_config_map: %s", api_response)
72         for deployment in self.deployment_list:
73             # pylint: disable=bad-continuation
74             with open(pkg_resources.resource_filename(
75                     'functest_kubernetes',
76                     'ims/{}-depl.yaml'.format(deployment))) as yfile:
77                 body = yaml.safe_load(yfile)
78                 resp = self.appsv1.create_namespaced_deployment(
79                     body=body, namespace="default")
80                 self.__logger.info("Deployment %s created", resp.metadata.name)
81                 self.__logger.debug(
82                     "create_namespaced_deployment: %s", api_response)
83         for service in self.deployment_list:
84             # pylint: disable=bad-continuation
85             with open(pkg_resources.resource_filename(
86                     'functest_kubernetes',
87                     'ims/{}-svc.yaml'.format(service))) as yfile:
88                 body = yaml.safe_load(yfile)
89                 resp = self.corev1.create_namespaced_service(
90                     body=body, namespace="default")
91                 self.__logger.info("Service %s created", resp.metadata.name)
92                 self.__logger.debug(
93                     "create_namespaced_service: %s", api_response)
94         # pylint: disable=no-member
95         status = self.deployment_list[:]
96         watch_deployment = watch.Watch()
97         for event in watch_deployment.stream(
98                 func=self.appsv1.list_namespaced_deployment,
99                 namespace=self.namespace, timeout_seconds=self.watch_timeout):
100             self.__logger.debug(event)
101             if event["object"].status.ready_replicas == 1:
102                 if event['object'].metadata.name in status:
103                     status.remove(event['object'].metadata.name)
104                     self.__logger.info(
105                         "%s started in %0.2f sec",
106                         event['object'].metadata.name,
107                         time.time()-self.start_time)
108             if not status:
109                 watch_deployment.stop()
110         self.result = 1/2 * 100
111
112     def test_vnf(self):
113         """Test vIMS as proposed by clearwater-live-test
114
115         It leverages on an unofficial Clearwater docker to allow testing from
116         the Kubernetes cluster.
117
118         See https://github.com/Metaswitch/clearwater-live-test for more details
119         """
120         container = client.V1Container(
121             name=self.test_container_name, image=self.test_image_name)
122         spec = client.V1PodSpec(containers=[container], restart_policy="Never")
123         metadata = client.V1ObjectMeta(name=self.test_container_name)
124         body = client.V1Pod(metadata=metadata, spec=spec)
125         api_response = self.corev1.create_namespaced_pod(self.namespace, body)
126         watch_deployment = watch.Watch()
127         for event in watch_deployment.stream(
128                 func=self.corev1.list_namespaced_pod,
129                 namespace=self.namespace, timeout_seconds=self.watch_timeout):
130             self.__logger.debug(event)
131             if event["object"].metadata.name == self.test_container_name:
132                 if (event["object"].status.phase == 'Succeeded' or
133                         event["object"].status.phase == 'Failed'):
134                     watch_deployment.stop()
135         api_response = self.corev1.read_namespaced_pod_log(
136             name=self.test_container_name, namespace=self.namespace)
137         self.__logger.info(api_response)
138         vims_test_result = {}
139         try:
140             grp = re.search(
141                 r'^(\d+) failures out of (\d+) tests run.*\n'
142                 r'(\d+) tests skipped$', api_response,
143                 re.MULTILINE | re.DOTALL)
144             assert grp
145             vims_test_result["failures"] = int(grp.group(1))
146             vims_test_result["total"] = int(grp.group(2))
147             vims_test_result["skipped"] = int(grp.group(3))
148             vims_test_result['passed'] = (
149                 int(grp.group(2)) - int(grp.group(3)) - int(grp.group(1)))
150             if vims_test_result['total'] - vims_test_result['skipped'] > 0:
151                 vnf_test_rate = vims_test_result['passed'] / (
152                     vims_test_result['total'] - vims_test_result['skipped'])
153             else:
154                 vnf_test_rate = 0
155             self.result += 1/2 * 100 * vnf_test_rate
156         except Exception:  # pylint: disable=broad-except
157             self.__logger.exception("Cannot parse live tests results")
158
159     def run(self, **kwargs):
160         self.start_time = time.time()
161         try:
162             self.deploy_vnf()
163             self.test_vnf()
164         except client.rest.ApiException:
165             self.__logger.exception("Cannot deploy and test vIms")
166         self.stop_time = time.time()
167
168     def clean(self):
169         try:
170             api_response = self.corev1.delete_namespaced_config_map(
171                 name=self.metadata_name, namespace=self.namespace)
172             self.__logger.debug(
173                 "delete_namespaced_config_map: %s", api_response)
174         except client.rest.ApiException:
175             pass
176         try:
177             api_response = self.corev1.delete_namespaced_pod(
178                 name=self.test_container_name, namespace=self.namespace)
179             self.__logger.debug("delete_namespaced_pod: %s", api_response)
180         except client.rest.ApiException:
181             pass
182         for deployment in self.deployment_list:
183             try:
184                 api_response = self.appsv1.delete_namespaced_deployment(
185                     name=deployment, namespace=self.namespace)
186                 self.__logger.debug(
187                     "delete_namespaced_deployment: %s", api_response)
188             except client.rest.ApiException:
189                 pass
190             try:
191                 api_response = self.corev1.delete_namespaced_service(
192                     name=deployment, namespace=self.namespace)
193                 self.__logger.debug(
194                     "delete_namespaced_service: %s", api_response)
195             except client.rest.ApiException:
196                 pass