Merge "Make K8s security tests namespace aware"
[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: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         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         self.result = 1/2 * 100
114
115     def test_vnf(self):
116         """Test vIMS as proposed by clearwater-live-test
117
118         It leverages on an unofficial Clearwater docker to allow testing from
119         the Kubernetes cluster.
120
121         See https://github.com/Metaswitch/clearwater-live-test for more details
122         """
123         container = client.V1Container(
124             name=self.test_container_name, image=self.test_image_name,
125             command=["rake", "test[{}]".format(self.zone),
126                      "PROXY=bono.{}".format(self.zone),
127                      "ELLIS=ellis.{}".format(self.zone),
128                      "SIGNUP_CODE=secret", "--trace"])
129         spec = client.V1PodSpec(containers=[container], restart_policy="Never")
130         metadata = client.V1ObjectMeta(name=self.test_container_name)
131         body = client.V1Pod(metadata=metadata, spec=spec)
132         api_response = self.corev1.create_namespaced_pod(self.namespace, body)
133         watch_deployment = watch.Watch()
134         for event in watch_deployment.stream(
135                 func=self.corev1.list_namespaced_pod,
136                 namespace=self.namespace, timeout_seconds=self.watch_timeout):
137             self.__logger.debug(event)
138             if event["object"].metadata.name == self.test_container_name:
139                 if (event["object"].status.phase == 'Succeeded'
140                         or event["object"].status.phase == 'Failed'):
141                     watch_deployment.stop()
142         api_response = self.corev1.read_namespaced_pod_log(
143             name=self.test_container_name, namespace=self.namespace)
144         self.__logger.info(api_response)
145         vims_test_result = {}
146         try:
147             grp = re.search(
148                 r'^(\d+) failures out of (\d+) tests run.*\n'
149                 r'(\d+) tests skipped$', api_response,
150                 re.MULTILINE | re.DOTALL)
151             assert grp
152             vims_test_result["failures"] = int(grp.group(1))
153             vims_test_result["total"] = int(grp.group(2))
154             vims_test_result["skipped"] = int(grp.group(3))
155             vims_test_result['passed'] = (
156                 int(grp.group(2)) - int(grp.group(3)) - int(grp.group(1)))
157             if vims_test_result['total'] - vims_test_result['skipped'] > 0:
158                 vnf_test_rate = vims_test_result['passed'] / (
159                     vims_test_result['total'] - vims_test_result['skipped'])
160             else:
161                 vnf_test_rate = 0
162             self.result += 1/2 * 100 * vnf_test_rate
163         except Exception:  # pylint: disable=broad-except
164             self.__logger.exception("Cannot parse live tests results")
165
166     def run(self, **kwargs):
167         self.start_time = time.time()
168         try:
169             self.deploy_vnf()
170             self.test_vnf()
171         except client.rest.ApiException:
172             self.__logger.exception("Cannot deploy and test vIms")
173         self.stop_time = time.time()
174
175     def clean(self):
176         try:
177             api_response = self.corev1.delete_namespaced_config_map(
178                 name=self.metadata_name, namespace=self.namespace)
179             self.__logger.debug(
180                 "delete_namespaced_config_map: %s", api_response)
181         except client.rest.ApiException:
182             pass
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         for deployment in self.deployment_list:
190             try:
191                 api_response = self.appsv1.delete_namespaced_deployment(
192                     name=deployment, namespace=self.namespace)
193                 self.__logger.debug(
194                     "delete_namespaced_deployment: %s", api_response)
195             except client.rest.ApiException:
196                 pass
197             try:
198                 api_response = self.corev1.delete_namespaced_service(
199                     name=deployment, namespace=self.namespace)
200                 self.__logger.debug(
201                     "delete_namespaced_service: %s", api_response)
202             except client.rest.ApiException:
203                 pass
204         try:
205             api_response = self.corev1.delete_namespace(self.namespace)
206             self.__logger.debug("delete_namespace: %s", self.namespace)
207         except client.rest.ApiException:
208             pass