3 # Copyright (c) 2020 Orange and others.
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
10 """Deploy and test Clearwater vIMS using Kubernetes"""
12 from __future__ import division
22 from jinja2 import Template
23 from kubernetes import client
24 from kubernetes import config
25 from kubernetes import watch
27 from xtesting.core import testcase
30 class Vims(testcase.TestCase): # pylint: disable=too-many-instance-attributes
31 """Deploy and test Clearwater vIMS using Kubernetes
33 It leverage on the Python kubernetes client to apply operation proposed by
36 See https://github.com/Metaswitch/clearwater-docker for more details
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")
46 __logger = logging.getLogger(__name__)
49 "astaire", "bono", "cassandra", "chronos", "ellis", "etcd", "homer",
50 "homestead", "homestead-prov", "ralf", "sprout"]
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'
62 def prepare_vnf(self):
63 """Prepare vIMS as proposed by clearwater-live-test
65 It creates a dedicated namespace and the configmap needed.
67 See https://github.com/Metaswitch/clearwater-live-test for more details
69 api_response = self.corev1.create_namespace(
70 client.V1Namespace(metadata=client.V1ObjectMeta(
71 generate_name=self.ns_generate_name,
72 labels={"pod-security.kubernetes.io/enforce": "baseline"})))
73 self.namespace = api_response.metadata.name
74 self.__logger.debug("create_namespace: %s", api_response)
75 self.zone = f'{self.namespace}.svc.cluster.local'
76 metadata = client.V1ObjectMeta(
77 name=self.metadata_name, namespace=self.namespace)
78 body = client.V1ConfigMap(
80 data={"ADDITIONAL_SHARED_CONFIG": "", "ZONE": self.zone})
81 api_response = self.corev1.create_namespaced_config_map(
82 self.namespace, body=body)
83 self.__logger.debug("create_namespaced_config_map: %s", api_response)
87 """Deploy vIMS as proposed by clearwater-docker
89 It must be overriden on purpose.
91 See https://github.com/Metaswitch/clearwater-docker for more details
95 """Wait vIMS is up and running"""
97 status = self.deployment_list.copy()
98 watch_deployment = watch.Watch()
99 for event in watch_deployment.stream(
100 func=self.appsv1.list_namespaced_deployment,
101 namespace=self.namespace, timeout_seconds=self.watch_timeout):
102 self.__logger.debug(event)
103 if event["object"].status.ready_replicas == 1:
104 if event['object'].metadata.name in status:
105 status.remove(event['object'].metadata.name)
107 "%s started in %0.2f sec",
108 event['object'].metadata.name,
109 time.time()-self.start_time)
111 watch_deployment.stop()
113 self.result = 1/2 * 100
115 self.__logger.error("Cannot deploy vIMS")
119 """Test vIMS as proposed by clearwater-live-test
121 It leverages an unofficial Clearwater docker to allow testing from
122 the Kubernetes cluster.
124 See https://github.com/Metaswitch/clearwater-live-test for more details
127 assert self.namespace
129 container = client.V1Container(
130 name=self.test_container_name, image=self.test_image_name,
131 command=["rake", f"test[{self.zone}]",
132 f"PROXY=bono.{self.zone}",
133 f"ELLIS=ellis.{self.zone}",
134 "SIGNUP_CODE=secret", "--trace"])
135 spec = client.V1PodSpec(containers=[container], restart_policy="Never")
136 metadata = client.V1ObjectMeta(name=self.test_container_name)
137 body = client.V1Pod(metadata=metadata, spec=spec)
138 api_response = self.corev1.create_namespaced_pod(self.namespace, body)
139 watch_deployment = watch.Watch()
140 for event in watch_deployment.stream(
141 func=self.corev1.list_namespaced_pod,
142 namespace=self.namespace, timeout_seconds=self.watch_timeout):
143 self.__logger.debug(event)
144 if event["object"].metadata.name == self.test_container_name:
145 if event["object"].status.phase in ('Succeeded', '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 = {}
153 r'^(\d+) failures out of (\d+) tests run.*\n'
154 r'(\d+) tests skipped$', api_response,
155 re.MULTILINE | re.DOTALL)
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'])
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")
171 def run(self, **kwargs):
172 self.start_time = time.time()
178 except client.rest.ApiException:
179 self.__logger.exception("Cannot deploy and test vIms")
180 self.stop_time = time.time()
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:
190 api_response = self.corev1.delete_namespaced_config_map(
191 name=self.metadata_name, namespace=self.namespace)
193 "delete_namespaced_config_map: %s", api_response)
194 except client.rest.ApiException:
197 api_response = self.corev1.delete_namespace(self.namespace)
198 self.__logger.debug("delete_namespace: %s", self.namespace)
199 except client.rest.ApiException:
204 """Deploy vIMS via kubectl as proposed by clearwater-docker
206 It leverages unofficial Clearwater dockers as proposed in the
209 See https://github.com/Metaswitch/clearwater-docker for more details
212 __logger = logging.getLogger(__name__)
214 def deploy_vnf(self):
215 """Deploy vIMS via kubectl as proposed by clearwater-docker
217 See https://github.com/Metaswitch/clearwater-docker for more details
219 assert self.namespace
220 for deployment in self.deployment_list:
221 with open(pkg_resources.resource_filename(
222 'functest_kubernetes',
223 f'ims/{deployment}-depl.yaml'),
224 encoding='utf-8') as yfile:
225 template = Template(yfile.read())
226 body = yaml.safe_load(template.render(
227 dockerhub_repo=os.getenv(
228 "DOCKERHUB_REPO", self.dockerhub_repo),
230 "QUAY_REPO", self.quay_repo)))
231 resp = self.appsv1.create_namespaced_deployment(
232 body=body, namespace=self.namespace)
233 self.__logger.info("Deployment %s created", resp.metadata.name)
235 "create_namespaced_deployment: %s", resp)
236 for service in self.deployment_list:
237 with open(pkg_resources.resource_filename(
238 'functest_kubernetes', f'ims/{service}-svc.yaml'),
239 encoding='utf-8') as yfile:
240 body = yaml.safe_load(yfile)
241 resp = self.corev1.create_namespaced_service(
242 body=body, namespace=self.namespace)
243 self.__logger.info("Service %s created", resp.metadata.name)
245 "create_namespaced_service: %s", resp)
248 for deployment in self.deployment_list:
250 api_response = self.appsv1.delete_namespaced_deployment(
251 name=deployment, namespace=self.namespace)
253 "delete_namespaced_deployment: %s", api_response)
254 except client.rest.ApiException:
257 api_response = self.corev1.delete_namespaced_service(
258 name=deployment, namespace=self.namespace)
260 "delete_namespaced_service: %s", api_response)
261 except client.rest.ApiException:
266 class HelmVims(Vims):
267 """Deploy vIMS via Helm as proposed by clearwater-docker
269 It leverages unofficial Clearwater dockers as proposed in the
272 See https://github.com/Metaswitch/clearwater-docker for more details
275 __logger = logging.getLogger(__name__)
277 def deploy_vnf(self):
278 """Deploy vIMS via Helm as proposed by clearwater-docker
280 See https://github.com/Metaswitch/clearwater-docker for more details
282 dockerhub_repo = os.getenv("DOCKERHUB_REPO", self.dockerhub_repo)
283 quay_repo = os.getenv("QUAY_REPO", self.quay_repo)
285 "helm", "install", "clearwater", "--set",
286 f"repo.dockerHub={dockerhub_repo},repo.quay={quay_repo}",
287 pkg_resources.resource_filename("functest_kubernetes", "ims/helm"),
288 "-n", self.namespace]
289 output = subprocess.check_output(cmd, stderr=subprocess.STDOUT)
290 self.__logger.debug(output.decode("utf-8"))
293 cmd = ["helm", "uninstall", "clearwater", "-n", self.namespace]
294 output = subprocess.check_output(cmd, stderr=subprocess.STDOUT)
295 self.__logger.debug(output.decode("utf-8"))