Update only the repo that was added instead of all repos
[osm/N2VC.git] / n2vc / tests / unit / test_k8s_helm_conn.py
1 ##
2 # Licensed under the Apache License, Version 2.0 (the "License"); you may
3 # not use this file except in compliance with the License. You may obtain
4 # a copy of the License at
5 #
6 # http://www.apache.org/licenses/LICENSE-2.0
7 #
8 # Unless required by applicable law or agreed to in writing, software
9 # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
10 # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
11 # License for the specific language governing permissions and limitations
12 # under the License.
13 #
14 # For those usages not covered by the Apache License, Version 2.0 please
15 # contact: alfonso.tiernosepulveda@telefonica.com
16 ##
17
18 import asynctest
19 import logging
20
21 from asynctest.mock import Mock
22 from osm_common.dbmemory import DbMemory
23 from osm_common.fslocal import FsLocal
24 from n2vc.k8s_helm_conn import K8sHelmConnector
25
26 __author__ = "Isabel Lloret <illoret@indra.es>"
27
28
29 class TestK8sHelmConn(asynctest.TestCase):
30 logging.basicConfig(level=logging.DEBUG)
31 logger = logging.getLogger(__name__)
32 logger.setLevel(logging.DEBUG)
33
34 async def setUp(self):
35 self.db = Mock(DbMemory())
36 self.fs = asynctest.Mock(FsLocal())
37 self.fs.path = "./tmp/"
38 self.namespace = "testk8s"
39 self.service_account = "osm"
40 self.cluster_id = "helm_cluster_id"
41 self.cluster_uuid = self.cluster_id
42 # pass fake kubectl and helm commands to make sure it does not call actual commands
43 K8sHelmConnector._check_file_exists = asynctest.Mock(return_value=True)
44 K8sHelmConnector._local_async_exec = asynctest.CoroutineMock(
45 return_value=(0, "")
46 )
47 cluster_dir = self.fs.path + self.cluster_id
48 self.kube_config = self.fs.path + self.cluster_id + "/.kube/config"
49 self.helm_home = self.fs.path + self.cluster_id + "/.helm"
50 self.env = {
51 "HELM_HOME": "{}/.helm".format(cluster_dir),
52 "KUBECONFIG": "{}/.kube/config".format(cluster_dir),
53 }
54 self.helm_conn = K8sHelmConnector(self.fs, self.db, log=self.logger)
55 self.logger.debug("Set up executed")
56
57 @asynctest.fail_on(active_handles=True)
58 async def test_init_env(self):
59 # TODO
60 pass
61
62 @asynctest.fail_on(active_handles=True)
63 async def test_repo_add(self):
64 repo_name = "bitnami"
65 repo_url = "https://charts.bitnami.com/bitnami"
66 self.helm_conn._local_async_exec = asynctest.CoroutineMock(return_value=("", 0))
67
68 await self.helm_conn.repo_add(self.cluster_uuid, repo_name, repo_url)
69
70 self.helm_conn.fs.sync.assert_called_once_with(from_path=self.cluster_id)
71 self.helm_conn.fs.reverse_sync.assert_called_once_with(
72 from_path=self.cluster_id
73 )
74 self.assertEqual(
75 self.helm_conn._local_async_exec.call_count,
76 2,
77 "local_async_exec expected 2 calls, called {}".format(
78 self.helm_conn._local_async_exec.call_count
79 ),
80 )
81
82 repo_update_command = (
83 "env KUBECONFIG=./tmp/helm_cluster_id/.kube/config /usr/bin/helm repo update {}"
84 ).format(repo_name)
85 repo_add_command = (
86 "env KUBECONFIG=./tmp/helm_cluster_id/.kube/config /usr/bin/helm repo add {} {}"
87 ).format(repo_name, repo_url)
88 calls = self.helm_conn._local_async_exec.call_args_list
89 call0_kargs = calls[0][1]
90 self.assertEqual(
91 call0_kargs.get("command"),
92 repo_add_command,
93 "Invalid repo add command: {}".format(call0_kargs.get("command")),
94 )
95 self.assertEqual(
96 call0_kargs.get("env"),
97 self.env,
98 "Invalid env for add command: {}".format(call0_kargs.get("env")),
99 )
100 call1_kargs = calls[1][1]
101 self.assertEqual(
102 call1_kargs.get("command"),
103 repo_update_command,
104 "Invalid repo update command: {}".format(call1_kargs.get("command")),
105 )
106 self.assertEqual(
107 call1_kargs.get("env"),
108 self.env,
109 "Invalid env for update command: {}".format(call1_kargs.get("env")),
110 )
111
112 @asynctest.fail_on(active_handles=True)
113 async def test_repo_list(self):
114 self.helm_conn._local_async_exec = asynctest.CoroutineMock(return_value=("", 0))
115
116 await self.helm_conn.repo_list(self.cluster_uuid)
117
118 self.helm_conn.fs.sync.assert_called_once_with(from_path=self.cluster_id)
119 self.helm_conn.fs.reverse_sync.assert_called_once_with(
120 from_path=self.cluster_id
121 )
122 command = "env KUBECONFIG=./tmp/helm_cluster_id/.kube/config /usr/bin/helm repo list --output yaml"
123 self.helm_conn._local_async_exec.assert_called_with(
124 command=command, env=self.env, raise_exception_on_error=False
125 )
126
127 @asynctest.fail_on(active_handles=True)
128 async def test_repo_remove(self):
129 self.helm_conn._local_async_exec = asynctest.CoroutineMock(return_value=("", 0))
130 repo_name = "bitnami"
131 await self.helm_conn.repo_remove(self.cluster_uuid, repo_name)
132
133 self.helm_conn.fs.sync.assert_called_once_with(from_path=self.cluster_id)
134 self.helm_conn.fs.reverse_sync.assert_called_once_with(
135 from_path=self.cluster_id
136 )
137 command = "env KUBECONFIG=./tmp/helm_cluster_id/.kube/config /usr/bin/helm repo remove {}".format(
138 repo_name
139 )
140 self.helm_conn._local_async_exec.assert_called_once_with(
141 command=command, env=self.env, raise_exception_on_error=True
142 )
143
144 @asynctest.fail_on(active_handles=True)
145 async def test_install(self):
146 kdu_model = "stable/openldap:1.2.2"
147 kdu_instance = "stable-openldap-0005399828"
148 db_dict = {}
149 self.helm_conn._local_async_exec = asynctest.CoroutineMock(return_value=("", 0))
150 self.helm_conn._status_kdu = asynctest.CoroutineMock(return_value=None)
151 self.helm_conn._store_status = asynctest.CoroutineMock()
152 self.helm_conn.generate_kdu_instance_name = Mock(return_value=kdu_instance)
153
154 await self.helm_conn.install(
155 self.cluster_uuid,
156 kdu_model,
157 kdu_instance,
158 atomic=True,
159 namespace=self.namespace,
160 db_dict=db_dict,
161 )
162
163 self.helm_conn.fs.sync.assert_called_once_with(from_path=self.cluster_id)
164 self.helm_conn.fs.reverse_sync.assert_called_once_with(
165 from_path=self.cluster_id
166 )
167 self.helm_conn._store_status.assert_called_with(
168 cluster_id=self.cluster_id,
169 kdu_instance=kdu_instance,
170 namespace=self.namespace,
171 db_dict=db_dict,
172 operation="install",
173 run_once=True,
174 check_every=0,
175 )
176 command = (
177 "env KUBECONFIG=./tmp/helm_cluster_id/.kube/config /usr/bin/helm install "
178 "--atomic --output yaml --timeout 300 "
179 "--name=stable-openldap-0005399828 --namespace testk8s stable/openldap "
180 "--version 1.2.2"
181 )
182 self.helm_conn._local_async_exec.assert_called_once_with(
183 command=command, env=self.env, raise_exception_on_error=False
184 )
185
186 @asynctest.fail_on(active_handles=True)
187 async def test_upgrade(self):
188 kdu_model = "stable/openldap:1.2.3"
189 kdu_instance = "stable-openldap-0005399828"
190 db_dict = {}
191 instance_info = {
192 "chart": "openldap-1.2.2",
193 "name": kdu_instance,
194 "namespace": self.namespace,
195 "revision": 1,
196 "status": "DEPLOYED",
197 }
198 self.helm_conn._local_async_exec = asynctest.CoroutineMock(return_value=("", 0))
199 self.helm_conn._store_status = asynctest.CoroutineMock()
200 self.helm_conn.get_instance_info = asynctest.CoroutineMock(
201 return_value=instance_info
202 )
203
204 await self.helm_conn.upgrade(
205 self.cluster_uuid, kdu_instance, kdu_model, atomic=True, db_dict=db_dict
206 )
207 self.helm_conn.fs.sync.assert_called_with(from_path=self.cluster_id)
208 self.helm_conn.fs.reverse_sync.assert_called_once_with(
209 from_path=self.cluster_id
210 )
211 self.helm_conn._store_status.assert_called_with(
212 cluster_id=self.cluster_id,
213 kdu_instance=kdu_instance,
214 namespace=self.namespace,
215 db_dict=db_dict,
216 operation="upgrade",
217 run_once=True,
218 check_every=0,
219 )
220 command = (
221 "env KUBECONFIG=./tmp/helm_cluster_id/.kube/config /usr/bin/helm upgrade "
222 "--atomic --output yaml --timeout 300 stable-openldap-0005399828 stable/openldap --version 1.2.3"
223 )
224 self.helm_conn._local_async_exec.assert_called_once_with(
225 command=command, env=self.env, raise_exception_on_error=False
226 )
227
228 @asynctest.fail_on(active_handles=True)
229 async def test_rollback(self):
230 kdu_instance = "stable-openldap-0005399828"
231 db_dict = {}
232 instance_info = {
233 "chart": "openldap-1.2.3",
234 "name": kdu_instance,
235 "namespace": self.namespace,
236 "revision": 2,
237 "status": "DEPLOYED",
238 }
239 self.helm_conn._local_async_exec = asynctest.CoroutineMock(return_value=("", 0))
240 self.helm_conn._store_status = asynctest.CoroutineMock()
241 self.helm_conn.get_instance_info = asynctest.CoroutineMock(
242 return_value=instance_info
243 )
244
245 await self.helm_conn.rollback(
246 self.cluster_uuid, kdu_instance=kdu_instance, revision=1, db_dict=db_dict
247 )
248 self.helm_conn.fs.sync.assert_called_with(from_path=self.cluster_id)
249 self.helm_conn.fs.reverse_sync.assert_called_once_with(
250 from_path=self.cluster_id
251 )
252 self.helm_conn._store_status.assert_called_with(
253 cluster_id=self.cluster_id,
254 kdu_instance=kdu_instance,
255 namespace=self.namespace,
256 db_dict=db_dict,
257 operation="rollback",
258 run_once=True,
259 check_every=0,
260 )
261 command = (
262 "env KUBECONFIG=./tmp/helm_cluster_id/.kube/config "
263 "/usr/bin/helm rollback stable-openldap-0005399828 1 --wait"
264 )
265 self.helm_conn._local_async_exec.assert_called_once_with(
266 command=command, env=self.env, raise_exception_on_error=False
267 )
268
269 @asynctest.fail_on(active_handles=True)
270 async def test_uninstall(self):
271 kdu_instance = "stable-openldap-0005399828"
272 instance_info = {
273 "chart": "openldap-1.2.2",
274 "name": kdu_instance,
275 "namespace": self.namespace,
276 "revision": 3,
277 "status": "DEPLOYED",
278 }
279 self.helm_conn._local_async_exec = asynctest.CoroutineMock(return_value=("", 0))
280 self.helm_conn._store_status = asynctest.CoroutineMock()
281 self.helm_conn.get_instance_info = asynctest.CoroutineMock(
282 return_value=instance_info
283 )
284
285 await self.helm_conn.uninstall(self.cluster_uuid, kdu_instance)
286 self.helm_conn.fs.sync.assert_called_with(from_path=self.cluster_id)
287 self.helm_conn.fs.reverse_sync.assert_called_once_with(
288 from_path=self.cluster_id
289 )
290 command = "env KUBECONFIG=./tmp/helm_cluster_id/.kube/config /usr/bin/helm delete --purge {}".format(
291 kdu_instance
292 )
293 self.helm_conn._local_async_exec.assert_called_once_with(
294 command=command, env=self.env, raise_exception_on_error=True
295 )
296
297 @asynctest.fail_on(active_handles=True)
298 async def test_get_services(self):
299 kdu_instance = "test_services_1"
300 service = {"name": "testservice", "type": "LoadBalancer"}
301 self.helm_conn._local_async_exec_pipe = asynctest.CoroutineMock(
302 return_value=("", 0)
303 )
304 self.helm_conn._parse_services = Mock(return_value=["testservice"])
305 self.helm_conn._get_service = asynctest.CoroutineMock(return_value=service)
306
307 services = await self.helm_conn.get_services(
308 self.cluster_uuid, kdu_instance, self.namespace
309 )
310 self.helm_conn.fs.sync.assert_called_once_with(from_path=self.cluster_id)
311 self.helm_conn.fs.reverse_sync.assert_called_once_with(
312 from_path=self.cluster_id
313 )
314 self.helm_conn._parse_services.assert_called_once()
315 command1 = "env KUBECONFIG=./tmp/helm_cluster_id/.kube/config /usr/bin/helm get manifest {} ".format(
316 kdu_instance
317 )
318 command2 = "/usr/bin/kubectl get --namespace={} -f -".format(self.namespace)
319 self.helm_conn._local_async_exec_pipe.assert_called_once_with(
320 command1, command2, env=self.env, raise_exception_on_error=True
321 )
322 self.assertEqual(
323 services, [service], "Invalid service returned from get_service"
324 )
325
326 @asynctest.fail_on(active_handles=True)
327 async def test_get_service(self):
328 service_name = "service1"
329
330 self.helm_conn._local_async_exec = asynctest.CoroutineMock(return_value=("", 0))
331 await self.helm_conn.get_service(
332 self.cluster_uuid, service_name, self.namespace
333 )
334
335 self.helm_conn.fs.sync.assert_called_once_with(from_path=self.cluster_id)
336 self.helm_conn.fs.reverse_sync.assert_called_once_with(
337 from_path=self.cluster_id
338 )
339 command = (
340 "/usr/bin/kubectl --kubeconfig=./tmp/helm_cluster_id/.kube/config "
341 "--namespace=testk8s get service service1 -o=yaml"
342 )
343 self.helm_conn._local_async_exec.assert_called_once_with(
344 command=command, env=self.env, raise_exception_on_error=True
345 )
346
347 @asynctest.fail_on(active_handles=True)
348 async def test_inspect_kdu(self):
349 self.helm_conn._local_async_exec = asynctest.CoroutineMock(return_value=("", 0))
350
351 kdu_model = "stable/openldap:1.2.4"
352 repo_url = "https://kubernetes-charts.storage.googleapis.com/"
353 await self.helm_conn.inspect_kdu(kdu_model, repo_url)
354
355 command = (
356 "/usr/bin/helm inspect openldap --repo "
357 "https://kubernetes-charts.storage.googleapis.com/ "
358 "--version 1.2.4"
359 )
360 self.helm_conn._local_async_exec.assert_called_with(
361 command=command, encode_utf8=True
362 )
363
364 @asynctest.fail_on(active_handles=True)
365 async def test_help_kdu(self):
366 self.helm_conn._local_async_exec = asynctest.CoroutineMock(return_value=("", 0))
367
368 kdu_model = "stable/openldap:1.2.4"
369 repo_url = "https://kubernetes-charts.storage.googleapis.com/"
370 await self.helm_conn.help_kdu(kdu_model, repo_url)
371
372 command = (
373 "/usr/bin/helm inspect readme openldap --repo "
374 "https://kubernetes-charts.storage.googleapis.com/ "
375 "--version 1.2.4"
376 )
377 self.helm_conn._local_async_exec.assert_called_with(
378 command=command, encode_utf8=True
379 )
380
381 @asynctest.fail_on(active_handles=True)
382 async def test_values_kdu(self):
383 self.helm_conn._local_async_exec = asynctest.CoroutineMock(return_value=("", 0))
384
385 kdu_model = "stable/openldap:1.2.4"
386 repo_url = "https://kubernetes-charts.storage.googleapis.com/"
387 await self.helm_conn.values_kdu(kdu_model, repo_url)
388
389 command = (
390 "/usr/bin/helm inspect values openldap --repo "
391 "https://kubernetes-charts.storage.googleapis.com/ "
392 "--version 1.2.4"
393 )
394 self.helm_conn._local_async_exec.assert_called_with(
395 command=command, encode_utf8=True
396 )
397
398 @asynctest.fail_on(active_handles=True)
399 async def test_instances_list(self):
400 self.helm_conn._local_async_exec = asynctest.CoroutineMock(return_value=("", 0))
401
402 await self.helm_conn.instances_list(self.cluster_uuid)
403 self.helm_conn.fs.sync.assert_called_once_with(from_path=self.cluster_id)
404 self.helm_conn.fs.reverse_sync.assert_called_once_with(
405 from_path=self.cluster_id
406 )
407 command = "/usr/bin/helm list --output yaml"
408 self.helm_conn._local_async_exec.assert_called_once_with(
409 command=command, env=self.env, raise_exception_on_error=True
410 )
411
412 @asynctest.fail_on(active_handles=True)
413 async def test_status_kdu(self):
414 kdu_instance = "stable-openldap-0005399828"
415 self.helm_conn._local_async_exec = asynctest.CoroutineMock(return_value=("", 0))
416
417 await self.helm_conn._status_kdu(
418 self.cluster_id, kdu_instance, self.namespace, yaml_format=True
419 )
420 command = (
421 "env KUBECONFIG=./tmp/helm_cluster_id/.kube/config /usr/bin/helm status {} --output yaml"
422 ).format(kdu_instance)
423 self.helm_conn._local_async_exec.assert_called_once_with(
424 command=command,
425 env=self.env,
426 raise_exception_on_error=True,
427 show_error_log=False,
428 )
429
430 @asynctest.fail_on(active_handles=True)
431 async def test_store_status(self):
432 kdu_instance = "stable-openldap-0005399828"
433 db_dict = {}
434 status = {
435 "info": {
436 "description": "Install complete",
437 "status": {
438 "code": "1",
439 "notes": "The openldap helm chart has been installed",
440 },
441 }
442 }
443 self.helm_conn._status_kdu = asynctest.CoroutineMock(return_value=status)
444 self.helm_conn.write_app_status_to_db = asynctest.CoroutineMock(
445 return_value=status
446 )
447
448 await self.helm_conn._store_status(
449 cluster_id=self.cluster_id,
450 kdu_instance=kdu_instance,
451 namespace=self.namespace,
452 db_dict=db_dict,
453 operation="install",
454 run_once=True,
455 check_every=0,
456 )
457 self.helm_conn._status_kdu.assert_called_once_with(
458 cluster_id=self.cluster_id,
459 kdu_instance=kdu_instance,
460 namespace=self.namespace,
461 yaml_format=False,
462 )
463 self.helm_conn.write_app_status_to_db.assert_called_once_with(
464 db_dict=db_dict,
465 status="Install complete",
466 detailed_status=str(status),
467 operation="install",
468 )
469
470 @asynctest.fail_on(active_handles=True)
471 async def test_reset_uninstall_false(self):
472 self.helm_conn._uninstall_sw = asynctest.CoroutineMock()
473
474 await self.helm_conn.reset(self.cluster_uuid, force=False, uninstall_sw=False)
475 self.helm_conn.fs.sync.assert_called_once_with(from_path=self.cluster_id)
476 self.helm_conn.fs.file_delete.assert_called_once_with(
477 self.cluster_id, ignore_non_exist=True
478 )
479 self.helm_conn._uninstall_sw.assert_not_called()
480
481 @asynctest.fail_on(active_handles=True)
482 async def test_reset_uninstall(self):
483 kdu_instance = "stable-openldap-0021099429"
484 instances = [
485 {
486 "app_version": "2.4.48",
487 "chart": "openldap-1.2.3",
488 "name": kdu_instance,
489 "namespace": self.namespace,
490 "revision": "1",
491 "status": "deployed",
492 "updated": "2020-10-30 11:11:20.376744191 +0000 UTC",
493 }
494 ]
495 self.helm_conn._get_namespace = Mock(return_value=self.namespace)
496 self.helm_conn._uninstall_sw = asynctest.CoroutineMock()
497 self.helm_conn.instances_list = asynctest.CoroutineMock(return_value=instances)
498 self.helm_conn.uninstall = asynctest.CoroutineMock()
499
500 await self.helm_conn.reset(self.cluster_uuid, force=True, uninstall_sw=True)
501 self.helm_conn.fs.sync.assert_called_once_with(from_path=self.cluster_id)
502 self.helm_conn.fs.file_delete.assert_called_once_with(
503 self.cluster_id, ignore_non_exist=True
504 )
505 self.helm_conn._get_namespace.assert_called_once_with(
506 cluster_uuid=self.cluster_uuid
507 )
508 self.helm_conn.instances_list.assert_called_once_with(
509 cluster_uuid=self.cluster_uuid
510 )
511 self.helm_conn.uninstall.assert_called_once_with(
512 cluster_uuid=self.cluster_uuid, kdu_instance=kdu_instance
513 )
514 self.helm_conn._uninstall_sw.assert_called_once_with(
515 cluster_id=self.cluster_id, namespace=self.namespace
516 )
517
518 @asynctest.fail_on(active_handles=True)
519 async def test_uninstall_sw_namespace(self):
520 self.helm_conn._local_async_exec = asynctest.CoroutineMock(return_value=("", 0))
521
522 await self.helm_conn._uninstall_sw(self.cluster_id, self.namespace)
523 calls = self.helm_conn._local_async_exec.call_args_list
524 self.assertEqual(
525 len(calls), 3, "To uninstall should have executed three commands"
526 )
527 call0_kargs = calls[0][1]
528 command_0 = "/usr/bin/helm --kubeconfig={} --home={} reset".format(
529 self.kube_config, self.helm_home
530 )
531 self.assertEqual(
532 call0_kargs,
533 {"command": command_0, "raise_exception_on_error": True, "env": self.env},
534 "Invalid args for first call to local_exec",
535 )
536 call1_kargs = calls[1][1]
537 command_1 = (
538 "/usr/bin/kubectl --kubeconfig={} delete "
539 "clusterrolebinding.rbac.authorization.k8s.io/osm-tiller-cluster-rule".format(
540 self.kube_config
541 )
542 )
543 self.assertEqual(
544 call1_kargs,
545 {"command": command_1, "raise_exception_on_error": False, "env": self.env},
546 "Invalid args for second call to local_exec",
547 )
548 call2_kargs = calls[2][1]
549 command_2 = (
550 "/usr/bin/kubectl --kubeconfig={} --namespace {} delete "
551 "serviceaccount/{}".format(
552 self.kube_config, self.namespace, self.service_account
553 )
554 )
555 self.assertEqual(
556 call2_kargs,
557 {"command": command_2, "raise_exception_on_error": False, "env": self.env},
558 "Invalid args for third call to local_exec",
559 )