Feature 10239: Distributed VCA
[osm/N2VC.git] / n2vc / tests / unit / test_libjuju.py
1 # Copyright 2020 Canonical Ltd.
2 #
3 # Licensed under the Apache License, Version 2.0 (the "License");
4 # you may not use this file except in compliance with the License.
5 # You may obtain a copy of the License at
6 #
7 # http://www.apache.org/licenses/LICENSE-2.0
8 #
9 # Unless required by applicable law or agreed to in writing, software
10 # distributed under the License is distributed on an "AS IS" BASIS,
11 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 # See the License for the specific language governing permissions and
13 # limitations under the License.
14
15 import asyncio
16 import asynctest
17 import tempfile
18 from unittest.mock import Mock, patch
19 import juju
20 import kubernetes
21 from juju.errors import JujuAPIError
22 import logging
23 from .utils import FakeMachine, FakeApplication
24 from n2vc.libjuju import Libjuju
25 from n2vc.exceptions import (
26 JujuControllerFailedConnecting,
27 JujuMachineNotFound,
28 JujuApplicationNotFound,
29 JujuActionNotFound,
30 JujuApplicationExists,
31 JujuInvalidK8sConfiguration,
32 JujuLeaderUnitNotFound,
33 JujuError,
34 )
35 from n2vc.k8s_juju_conn import generate_rbac_id
36 from n2vc.tests.unit.utils import AsyncMock
37 from n2vc.vca.connection import Connection
38 from n2vc.vca.connection_data import ConnectionData
39
40
41 cacert = """-----BEGIN CERTIFICATE-----
42 SOMECERT
43 -----END CERTIFICATE-----"""
44
45
46 @asynctest.mock.patch("n2vc.libjuju.Controller")
47 class LibjujuTestCase(asynctest.TestCase):
48 @asynctest.mock.patch("n2vc.vca.connection_data.base64_to_cacert")
49 def setUp(
50 self,
51 mock_base64_to_cacert=None,
52 ):
53 self.loop = asyncio.get_event_loop()
54 self.db = Mock()
55 mock_base64_to_cacert.return_value = cacert
56 Connection._load_vca_connection_data = Mock()
57 vca_connection = Connection(AsyncMock())
58 vca_connection._data = ConnectionData(
59 **{
60 "endpoints": ["1.2.3.4:17070"],
61 "user": "user",
62 "secret": "secret",
63 "cacert": "cacert",
64 "pubkey": "pubkey",
65 "lxd-cloud": "cloud",
66 "lxd-credentials": "credentials",
67 "k8s-cloud": "k8s_cloud",
68 "k8s-credentials": "k8s_credentials",
69 "model-config": {},
70 "api-proxy": "api_proxy",
71 }
72 )
73 logging.disable(logging.CRITICAL)
74 self.libjuju = Libjuju(vca_connection, self.loop)
75 self.loop.run_until_complete(self.libjuju.disconnect())
76
77
78 @asynctest.mock.patch("juju.controller.Controller.connect")
79 @asynctest.mock.patch(
80 "juju.controller.Controller.api_endpoints",
81 new_callable=asynctest.CoroutineMock(return_value=["127.0.0.1:17070"]),
82 )
83 class GetControllerTest(LibjujuTestCase):
84 def setUp(self):
85 super(GetControllerTest, self).setUp()
86
87 def test_diff_endpoint(self, mock_api_endpoints, mock_connect):
88 self.libjuju.endpoints = []
89 controller = self.loop.run_until_complete(self.libjuju.get_controller())
90 self.assertIsInstance(controller, juju.controller.Controller)
91
92 @asynctest.mock.patch("n2vc.libjuju.Libjuju.disconnect_controller")
93 def test_exception(
94 self,
95 mock_disconnect_controller,
96 mock_api_endpoints,
97 mock_connect,
98 ):
99 self.libjuju.endpoints = []
100
101 mock_connect.side_effect = Exception()
102 controller = None
103 with self.assertRaises(JujuControllerFailedConnecting):
104 controller = self.loop.run_until_complete(self.libjuju.get_controller())
105 self.assertIsNone(controller)
106 mock_disconnect_controller.assert_called_once()
107
108 def test_same_endpoint_get_controller(self, mock_api_endpoints, mock_connect):
109 self.libjuju.endpoints = ["127.0.0.1:17070"]
110 controller = self.loop.run_until_complete(self.libjuju.get_controller())
111 self.assertIsInstance(controller, juju.controller.Controller)
112
113
114 class DisconnectTest(LibjujuTestCase):
115 def setUp(self):
116 super(DisconnectTest, self).setUp()
117
118 @asynctest.mock.patch("juju.model.Model.disconnect")
119 def test_disconnect_model(self, mock_disconnect):
120 self.loop.run_until_complete(self.libjuju.disconnect_model(juju.model.Model()))
121 mock_disconnect.assert_called_once()
122
123 @asynctest.mock.patch("juju.controller.Controller.disconnect")
124 def test_disconnect_controller(self, mock_disconnect):
125 self.loop.run_until_complete(
126 self.libjuju.disconnect_controller(juju.controller.Controller())
127 )
128 mock_disconnect.assert_called_once()
129
130
131 @asynctest.mock.patch("n2vc.libjuju.Libjuju.get_controller")
132 @asynctest.mock.patch("n2vc.libjuju.Libjuju.model_exists")
133 @asynctest.mock.patch("juju.controller.Controller.add_model")
134 @asynctest.mock.patch("n2vc.libjuju.Libjuju.disconnect_controller")
135 @asynctest.mock.patch("n2vc.libjuju.Libjuju.disconnect_model")
136 class AddModelTest(LibjujuTestCase):
137 def setUp(self):
138 super(AddModelTest, self).setUp()
139
140 def test_existing_model(
141 self,
142 mock_disconnect_model,
143 mock_disconnect_controller,
144 mock_add_model,
145 mock_model_exists,
146 mock_get_controller,
147 ):
148 mock_model_exists.return_value = True
149
150 # This should not raise an exception
151 self.loop.run_until_complete(self.libjuju.add_model("existing_model", "cloud"))
152
153 mock_disconnect_controller.assert_called()
154
155 # TODO Check two job executing at the same time and one returning without doing anything.
156
157 def test_non_existing_model(
158 self,
159 mock_disconnect_model,
160 mock_disconnect_controller,
161 mock_add_model,
162 mock_model_exists,
163 mock_get_controller,
164 ):
165 mock_model_exists.return_value = False
166 mock_get_controller.return_value = juju.controller.Controller()
167
168 self.loop.run_until_complete(
169 self.libjuju.add_model("nonexisting_model", Mock())
170 )
171
172 mock_add_model.assert_called_once()
173 mock_disconnect_controller.assert_called()
174 mock_disconnect_model.assert_called()
175
176
177 @asynctest.mock.patch("n2vc.libjuju.Libjuju.get_controller")
178 @asynctest.mock.patch("n2vc.libjuju.Libjuju.get_model")
179 @asynctest.mock.patch("n2vc.libjuju.Libjuju.disconnect_model")
180 @asynctest.mock.patch("n2vc.libjuju.Libjuju.disconnect_controller")
181 @asynctest.mock.patch(
182 "juju.model.Model.applications", new_callable=asynctest.PropertyMock
183 )
184 @asynctest.mock.patch("juju.model.Model.get_action_status")
185 @asynctest.mock.patch("juju.model.Model.get_action_output")
186 @asynctest.mock.patch("n2vc.libjuju.Libjuju.get_actions")
187 class GetExecutedActionsTest(LibjujuTestCase):
188 def setUp(self):
189 super(GetExecutedActionsTest, self).setUp()
190
191 def test_exception(
192 self,
193 mock_get_actions,
194 mock_get_action_output,
195 mock_get_action_status,
196 mock_applications,
197 mock_disconnect_controller,
198 mock_disconnect_model,
199 mock_get_model,
200 mock_get_controller,
201 ):
202 mock_get_model.return_value = None
203 with self.assertRaises(JujuError):
204 self.loop.run_until_complete(self.libjuju.get_executed_actions("model"))
205
206 mock_get_controller.assert_called_once()
207 mock_disconnect_controller.assert_called_once()
208 mock_get_model.assert_called_once()
209 mock_disconnect_model.assert_not_called()
210
211 def test_success(
212 self,
213 mock_get_actions,
214 mock_get_action_output,
215 mock_get_action_status,
216 mock_applications,
217 mock_disconnect_controller,
218 mock_disconnect_model,
219 mock_get_model,
220 mock_get_controller,
221 ):
222 mock_get_model.return_value = juju.model.Model()
223 mock_applications.return_value = {"existing_app"}
224 mock_get_actions.return_value = {"action_name": "description"}
225 mock_get_action_status.return_value = {"id": "status"}
226 mock_get_action_output.return_value = {"output": "completed"}
227
228 executed_actions = self.loop.run_until_complete(
229 self.libjuju.get_executed_actions("model")
230 )
231 expected_result = [{'id': 'id', 'action': 'action_name',
232 'status': 'status', 'output': 'completed'}]
233 self.assertListEqual(expected_result, executed_actions)
234 self.assertIsInstance(executed_actions, list)
235
236 mock_get_controller.assert_called_once()
237 mock_get_model.assert_called_once()
238 mock_disconnect_controller.assert_called_once()
239 mock_disconnect_model.assert_called_once()
240
241
242 @asynctest.mock.patch("n2vc.libjuju.Libjuju.get_controller")
243 @asynctest.mock.patch("n2vc.libjuju.Libjuju.get_model")
244 @asynctest.mock.patch("n2vc.libjuju.Libjuju.disconnect_model")
245 @asynctest.mock.patch("n2vc.libjuju.Libjuju.disconnect_controller")
246 @asynctest.mock.patch("n2vc.libjuju.Libjuju._get_application")
247 class GetApplicationConfigsTest(LibjujuTestCase):
248 def setUp(self):
249 super(GetApplicationConfigsTest, self).setUp()
250
251 def test_exception(
252 self,
253 mock_get_application,
254 mock_disconnect_controller,
255 mock_disconnect_model,
256 mock_get_model,
257 mock_get_controller,
258 ):
259 mock_get_model.return_value = None
260 with self.assertRaises(JujuError):
261 self.loop.run_until_complete(
262 self.libjuju.get_application_configs("model", "app"))
263
264 mock_get_controller.assert_called_once()
265 mock_disconnect_controller.assert_called_once()
266 mock_get_model.assert_called_once()
267 mock_disconnect_model.assert_not_called()
268
269 def test_success(
270 self,
271 mock_get_application,
272 mock_disconnect_controller,
273 mock_disconnect_model,
274 mock_get_model,
275 mock_get_controller,
276 ):
277 mock_get_application.return_value = FakeApplication()
278 application_configs = self.loop.run_until_complete(self.libjuju
279 .get_application_configs("model", "app"))
280
281 self.assertEqual(application_configs, ["app_config"])
282
283 mock_get_controller.assert_called_once()
284 mock_get_model.assert_called_once()
285 mock_disconnect_controller.assert_called_once()
286 mock_disconnect_model.assert_called_once()
287
288
289 @asynctest.mock.patch("juju.controller.Controller.get_model")
290 class GetModelTest(LibjujuTestCase):
291 def setUp(self):
292 super(GetModelTest, self).setUp()
293
294 def test_get_model(
295 self,
296 mock_get_model,
297 ):
298 mock_get_model.return_value = juju.model.Model()
299 model = self.loop.run_until_complete(
300 self.libjuju.get_model(juju.controller.Controller(), "model")
301 )
302 self.assertIsInstance(model, juju.model.Model)
303
304
305 @asynctest.mock.patch("n2vc.libjuju.Libjuju.get_controller")
306 @asynctest.mock.patch("juju.controller.Controller.list_models")
307 class ModelExistsTest(LibjujuTestCase):
308 def setUp(self):
309 super(ModelExistsTest, self).setUp()
310
311 async def test_existing_model(
312 self,
313 mock_list_models,
314 mock_get_controller,
315 ):
316 mock_list_models.return_value = ["existing_model"]
317 self.assertTrue(
318 await self.libjuju.model_exists(
319 "existing_model", juju.controller.Controller()
320 )
321 )
322
323 @asynctest.mock.patch("n2vc.libjuju.Libjuju.disconnect_controller")
324 async def test_no_controller(
325 self,
326 mock_disconnect_controller,
327 mock_list_models,
328 mock_get_controller,
329 ):
330 mock_list_models.return_value = ["existing_model"]
331 mock_get_controller.return_value = juju.controller.Controller()
332 self.assertTrue(await self.libjuju.model_exists("existing_model"))
333 mock_disconnect_controller.assert_called_once()
334
335 async def test_non_existing_model(
336 self,
337 mock_list_models,
338 mock_get_controller,
339 ):
340 mock_list_models.return_value = ["existing_model"]
341 self.assertFalse(
342 await self.libjuju.model_exists(
343 "not_existing_model", juju.controller.Controller()
344 )
345 )
346
347
348 @asynctest.mock.patch("n2vc.libjuju.Libjuju.get_controller")
349 @asynctest.mock.patch("n2vc.libjuju.Libjuju.get_model")
350 @asynctest.mock.patch("n2vc.libjuju.Libjuju.disconnect_model")
351 @asynctest.mock.patch("n2vc.libjuju.Libjuju.disconnect_controller")
352 @asynctest.mock.patch("juju.model.Model.get_status")
353 class GetModelStatusTest(LibjujuTestCase):
354 def setUp(self):
355 super(GetModelStatusTest, self).setUp()
356
357 def test_success(
358 self,
359 mock_get_status,
360 mock_disconnect_controller,
361 mock_disconnect_model,
362 mock_get_model,
363 mock_get_controller,
364 ):
365 mock_get_model.return_value = juju.model.Model()
366 mock_get_status.return_value = {"status"}
367
368 status = self.loop.run_until_complete(self.libjuju.get_model_status("model"))
369
370 mock_get_status.assert_called_once()
371 mock_disconnect_controller.assert_called_once()
372 mock_disconnect_model.assert_called_once()
373
374 self.assertEqual(status, {"status"})
375
376 def test_exception(
377 self,
378 mock_get_status,
379 mock_disconnect_controller,
380 mock_disconnect_model,
381 mock_get_model,
382 mock_get_controller,
383 ):
384 mock_get_model.return_value = juju.model.Model()
385 mock_get_status.side_effect = Exception()
386 status = None
387 with self.assertRaises(Exception):
388 status = self.loop.run_until_complete(
389 self.libjuju.get_model_status("model")
390 )
391
392 mock_disconnect_controller.assert_called_once()
393 mock_disconnect_model.assert_called_once()
394
395 self.assertIsNone(status)
396
397
398 @asynctest.mock.patch("n2vc.libjuju.Libjuju.get_controller")
399 @asynctest.mock.patch("n2vc.libjuju.Libjuju.get_model")
400 @asynctest.mock.patch("n2vc.libjuju.Libjuju.disconnect_model")
401 @asynctest.mock.patch("n2vc.libjuju.Libjuju.disconnect_controller")
402 @asynctest.mock.patch("juju.model.Model.get_machines")
403 @asynctest.mock.patch("juju.model.Model.add_machine")
404 @asynctest.mock.patch("n2vc.juju_watcher.JujuModelWatcher.wait_for")
405 class CreateMachineTest(LibjujuTestCase):
406 def setUp(self):
407 super(CreateMachineTest, self).setUp()
408
409 def test_existing_machine(
410 self,
411 mock_wait_for,
412 mock_add_machine,
413 mock_get_machines,
414 mock_disconnect_controller,
415 mock_disconnect_model,
416 mock_get_model,
417 mock_get_controller,
418 ):
419 mock_get_model.return_value = juju.model.Model()
420 mock_get_machines.return_value = {"existing_machine": FakeMachine()}
421 machine, bool_res = self.loop.run_until_complete(
422 self.libjuju.create_machine("model", "existing_machine")
423 )
424
425 self.assertIsInstance(machine, FakeMachine)
426 self.assertFalse(bool_res)
427
428 mock_disconnect_controller.assert_called()
429 mock_disconnect_model.assert_called()
430
431 def test_non_existing_machine(
432 self,
433 mock_wait_for,
434 mock_add_machine,
435 mock_get_machines,
436 mock_disconnect_controller,
437 mock_disconnect_model,
438 mock_get_model,
439 mock_get_controller,
440 ):
441 machine = None
442 bool_res = None
443 mock_get_model.return_value = juju.model.Model()
444 with self.assertRaises(JujuMachineNotFound):
445 machine, bool_res = self.loop.run_until_complete(
446 self.libjuju.create_machine("model", "non_existing_machine")
447 )
448 self.assertIsNone(machine)
449 self.assertIsNone(bool_res)
450
451 mock_disconnect_controller.assert_called()
452 mock_disconnect_model.assert_called()
453
454 def test_no_machine(
455 self,
456 mock_wait_for,
457 mock_add_machine,
458 mock_get_machines,
459 mock_disconnect_controller,
460 mock_disconnect_model,
461 mock_get_model,
462 mock_get_controller,
463 ):
464 mock_get_model.return_value = juju.model.Model()
465 mock_add_machine.return_value = FakeMachine()
466
467 machine, bool_res = self.loop.run_until_complete(
468 self.libjuju.create_machine("model")
469 )
470
471 self.assertIsInstance(machine, FakeMachine)
472 self.assertTrue(bool_res)
473
474 mock_wait_for.assert_called_once()
475 mock_add_machine.assert_called_once()
476
477 mock_disconnect_controller.assert_called()
478 mock_disconnect_model.assert_called()
479
480
481 # TODO test provision machine
482
483
484 @asynctest.mock.patch("n2vc.libjuju.Libjuju.get_controller")
485 @asynctest.mock.patch("n2vc.libjuju.Libjuju.get_model")
486 @asynctest.mock.patch("n2vc.libjuju.Libjuju.disconnect_model")
487 @asynctest.mock.patch("n2vc.libjuju.Libjuju.disconnect_controller")
488 @asynctest.mock.patch("n2vc.juju_watcher.JujuModelWatcher.wait_for_model")
489 @asynctest.mock.patch("juju.model.Model.deploy")
490 class DeployTest(LibjujuTestCase):
491 def setUp(self):
492 super(DeployTest, self).setUp()
493
494 def test_deploy(
495 self,
496 mock_deploy,
497 mock_wait_for_model,
498 mock_disconnect_controller,
499 mock_disconnect_model,
500 mock_get_model,
501 mock_get_controller,
502 ):
503 mock_get_model.return_value = juju.model.Model()
504 self.loop.run_until_complete(
505 self.libjuju.deploy("cs:osm", "model", wait=True, timeout=0)
506 )
507 mock_deploy.assert_called_once()
508 mock_wait_for_model.assert_called_once()
509 mock_disconnect_controller.assert_called_once()
510 mock_disconnect_model.assert_called_once()
511
512 def test_deploy_no_wait(
513 self,
514 mock_deploy,
515 mock_wait_for_model,
516 mock_disconnect_controller,
517 mock_disconnect_model,
518 mock_get_model,
519 mock_get_controller,
520 ):
521 mock_get_model.return_value = juju.model.Model()
522 self.loop.run_until_complete(
523 self.libjuju.deploy("cs:osm", "model", wait=False, timeout=0)
524 )
525 mock_deploy.assert_called_once()
526 mock_wait_for_model.assert_not_called()
527 mock_disconnect_controller.assert_called_once()
528 mock_disconnect_model.assert_called_once()
529
530 def test_deploy_exception(
531 self,
532 mock_deploy,
533 mock_wait_for_model,
534 mock_disconnect_controller,
535 mock_disconnect_model,
536 mock_get_model,
537 mock_get_controller,
538 ):
539 mock_deploy.side_effect = Exception()
540 mock_get_model.return_value = juju.model.Model()
541 with self.assertRaises(Exception):
542 self.loop.run_until_complete(self.libjuju.deploy("cs:osm", "model"))
543 mock_deploy.assert_called_once()
544 mock_wait_for_model.assert_not_called()
545 mock_disconnect_controller.assert_called_once()
546 mock_disconnect_model.assert_called_once()
547
548
549 @asynctest.mock.patch("n2vc.libjuju.Libjuju.get_controller")
550 @asynctest.mock.patch("n2vc.libjuju.Libjuju.get_model")
551 @asynctest.mock.patch("n2vc.libjuju.Libjuju.disconnect_model")
552 @asynctest.mock.patch("n2vc.libjuju.Libjuju.disconnect_controller")
553 @asynctest.mock.patch(
554 "juju.model.Model.applications", new_callable=asynctest.PropertyMock
555 )
556 @asynctest.mock.patch("juju.model.Model.machines", new_callable=asynctest.PropertyMock)
557 @asynctest.mock.patch("juju.model.Model.deploy")
558 @asynctest.mock.patch("n2vc.juju_watcher.JujuModelWatcher.wait_for")
559 @asynctest.mock.patch("n2vc.libjuju.Libjuju.create_machine")
560 class DeployCharmTest(LibjujuTestCase):
561 def setUp(self):
562 super(DeployCharmTest, self).setUp()
563
564 def test_existing_app(
565 self,
566 mock_create_machine,
567 mock_wait_for,
568 mock_deploy,
569 mock_machines,
570 mock_applications,
571 mock_disconnect_controller,
572 mock_disconnect_model,
573 mock_get_model,
574 mock_get_controller,
575 ):
576 mock_get_model.return_value = juju.model.Model()
577 mock_applications.return_value = {"existing_app"}
578
579 application = None
580 with self.assertRaises(JujuApplicationExists):
581 application = self.loop.run_until_complete(
582 self.libjuju.deploy_charm(
583 "existing_app",
584 "path",
585 "model",
586 "machine",
587 )
588 )
589 self.assertIsNone(application)
590
591 mock_disconnect_controller.assert_called()
592 mock_disconnect_model.assert_called()
593
594 def test_non_existing_machine(
595 self,
596 mock_create_machine,
597 mock_wait_for,
598 mock_deploy,
599 mock_machines,
600 mock_applications,
601 mock_disconnect_controller,
602 mock_disconnect_model,
603 mock_get_model,
604 mock_get_controller,
605 ):
606 mock_get_model.return_value = juju.model.Model()
607 mock_machines.return_value = {"existing_machine": FakeMachine()}
608 application = None
609 with self.assertRaises(JujuMachineNotFound):
610 application = self.loop.run_until_complete(
611 self.libjuju.deploy_charm(
612 "app",
613 "path",
614 "model",
615 "machine",
616 )
617 )
618
619 self.assertIsNone(application)
620
621 mock_disconnect_controller.assert_called()
622 mock_disconnect_model.assert_called()
623
624 def test_2_units(
625 self,
626 mock_create_machine,
627 mock_wait_for,
628 mock_deploy,
629 mock_machines,
630 mock_applications,
631 mock_disconnect_controller,
632 mock_disconnect_model,
633 mock_get_model,
634 mock_get_controller,
635 ):
636 mock_get_model.return_value = juju.model.Model()
637 mock_machines.return_value = {"existing_machine": FakeMachine()}
638 mock_create_machine.return_value = (FakeMachine(), "other")
639 mock_deploy.return_value = FakeApplication()
640 application = self.loop.run_until_complete(
641 self.libjuju.deploy_charm(
642 "app",
643 "path",
644 "model",
645 "existing_machine",
646 num_units=2,
647 )
648 )
649
650 self.assertIsInstance(application, FakeApplication)
651
652 mock_deploy.assert_called_once()
653 mock_wait_for.assert_called_once()
654
655 mock_create_machine.assert_called_once()
656
657 mock_disconnect_controller.assert_called()
658 mock_disconnect_model.assert_called()
659
660 def test_1_unit(
661 self,
662 mock_create_machine,
663 mock_wait_for,
664 mock_deploy,
665 mock_machines,
666 mock_applications,
667 mock_disconnect_controller,
668 mock_disconnect_model,
669 mock_get_model,
670 mock_get_controller,
671 ):
672 mock_get_model.return_value = juju.model.Model()
673 mock_machines.return_value = {"existing_machine": FakeMachine()}
674 mock_deploy.return_value = FakeApplication()
675 application = self.loop.run_until_complete(
676 self.libjuju.deploy_charm("app", "path", "model", "existing_machine")
677 )
678
679 self.assertIsInstance(application, FakeApplication)
680
681 mock_deploy.assert_called_once()
682 mock_wait_for.assert_called_once()
683
684 mock_disconnect_controller.assert_called()
685 mock_disconnect_model.assert_called()
686
687
688 @asynctest.mock.patch(
689 "juju.model.Model.applications", new_callable=asynctest.PropertyMock
690 )
691 class GetApplicationTest(LibjujuTestCase):
692 def setUp(self):
693 super(GetApplicationTest, self).setUp()
694
695 def test_existing_application(
696 self,
697 mock_applications,
698 ):
699 mock_applications.return_value = {"existing_app": "exists"}
700 model = juju.model.Model()
701 result = self.libjuju._get_application(model, "existing_app")
702 self.assertEqual(result, "exists")
703
704 def test_non_existing_application(
705 self,
706 mock_applications,
707 ):
708 mock_applications.return_value = {"existing_app": "exists"}
709 model = juju.model.Model()
710 result = self.libjuju._get_application(model, "nonexisting_app")
711 self.assertIsNone(result)
712
713
714 @asynctest.mock.patch("n2vc.libjuju.Libjuju.get_controller")
715 @asynctest.mock.patch("n2vc.libjuju.Libjuju.get_model")
716 @asynctest.mock.patch("n2vc.libjuju.Libjuju.disconnect_model")
717 @asynctest.mock.patch("n2vc.libjuju.Libjuju.disconnect_controller")
718 @asynctest.mock.patch("n2vc.libjuju.Libjuju._get_application")
719 @asynctest.mock.patch("n2vc.juju_watcher.JujuModelWatcher.wait_for")
720 @asynctest.mock.patch("juju.model.Model.get_action_output")
721 @asynctest.mock.patch("juju.model.Model.get_action_status")
722 class ExecuteActionTest(LibjujuTestCase):
723 def setUp(self):
724 super(ExecuteActionTest, self).setUp()
725
726 def test_no_application(
727 self,
728 mock_get_action_status,
729 mock_get_action_output,
730 mock_wait_for,
731 mock__get_application,
732 mock_disconnect_controller,
733 mock_disconnect_model,
734 mock_get_model,
735 mock_get_controller,
736 ):
737 mock__get_application.return_value = None
738 mock_get_model.return_value = juju.model.Model()
739 output = None
740 status = None
741 with self.assertRaises(JujuApplicationNotFound):
742 output, status = self.loop.run_until_complete(
743 self.libjuju.execute_action(
744 "app",
745 "model",
746 "action",
747 )
748 )
749 self.assertIsNone(output)
750 self.assertIsNone(status)
751
752 mock_disconnect_controller.assert_called()
753 mock_disconnect_model.assert_called()
754
755 def test_no_action(
756 self,
757 mock_get_action_status,
758 mock_get_action_output,
759 mock_wait_for,
760 mock__get_application,
761 mock_disconnect_controller,
762 mock_disconnect_model,
763 mock_get_model,
764 mock_get_controller,
765 ):
766
767 mock_get_model.return_value = juju.model.Model()
768 mock__get_application.return_value = FakeApplication()
769 output = None
770 status = None
771 with self.assertRaises(JujuActionNotFound):
772 output, status = self.loop.run_until_complete(
773 self.libjuju.execute_action(
774 "app",
775 "model",
776 "action",
777 )
778 )
779 self.assertIsNone(output)
780 self.assertIsNone(status)
781
782 mock_disconnect_controller.assert_called()
783 mock_disconnect_model.assert_called()
784
785 @asynctest.mock.patch("asyncio.sleep")
786 @asynctest.mock.patch("n2vc.tests.unit.utils.FakeUnit.is_leader_from_status")
787 def test_no_leader(
788 self,
789 mock_is_leader_from_status,
790 mock_sleep,
791 mock_get_action_status,
792 mock_get_action_output,
793 mock_wait_for,
794 mock__get_application,
795 mock_disconnect_controller,
796 mock_disconnect_model,
797 mock_get_model,
798 mock_get_controller,
799 ):
800 mock_get_model.return_value = juju.model.Model()
801 mock__get_application.return_value = FakeApplication()
802 mock_is_leader_from_status.return_value = False
803 output = None
804 status = None
805 with self.assertRaises(JujuLeaderUnitNotFound):
806 output, status = self.loop.run_until_complete(
807 self.libjuju.execute_action(
808 "app",
809 "model",
810 "action",
811 )
812 )
813 self.assertIsNone(output)
814 self.assertIsNone(status)
815
816 mock_disconnect_controller.assert_called()
817 mock_disconnect_model.assert_called()
818
819 def test_succesful_exec(
820 self,
821 mock_get_action_status,
822 mock_get_action_output,
823 mock_wait_for,
824 mock__get_application,
825 mock_disconnect_controller,
826 mock_disconnect_model,
827 mock_get_model,
828 mock_get_controller,
829 ):
830 mock_get_model.return_value = juju.model.Model()
831 mock__get_application.return_value = FakeApplication()
832 mock_get_action_output.return_value = "output"
833 mock_get_action_status.return_value = {"id": "status"}
834 output, status = self.loop.run_until_complete(
835 self.libjuju.execute_action("app", "model", "existing_action")
836 )
837 self.assertEqual(output, "output")
838 self.assertEqual(status, "status")
839
840 mock_wait_for.assert_called_once()
841
842 mock_disconnect_controller.assert_called()
843 mock_disconnect_model.assert_called()
844
845
846 @asynctest.mock.patch("n2vc.libjuju.Libjuju.get_controller")
847 @asynctest.mock.patch("n2vc.libjuju.Libjuju.get_model")
848 @asynctest.mock.patch("n2vc.libjuju.Libjuju.disconnect_model")
849 @asynctest.mock.patch("n2vc.libjuju.Libjuju.disconnect_controller")
850 @asynctest.mock.patch("n2vc.libjuju.Libjuju._get_application")
851 class GetActionTest(LibjujuTestCase):
852 def setUp(self):
853 super(GetActionTest, self).setUp()
854
855 def test_exception(
856 self,
857 mock_get_application,
858 mock_disconnect_controller,
859 mock_disconnect_model,
860 mock_get_model,
861 mock_get_controller,
862 ):
863 mock_get_application.side_effect = Exception()
864 actions = None
865 with self.assertRaises(Exception):
866 actions = self.loop.run_until_complete(
867 self.libjuju.get_actions("app", "model")
868 )
869
870 self.assertIsNone(actions)
871 mock_disconnect_controller.assert_called_once()
872 mock_disconnect_model.assert_called_once()
873
874 def test_success(
875 self,
876 mock_get_application,
877 mock_disconnect_controller,
878 mock_disconnect_model,
879 mock_get_model,
880 mock_get_controller,
881 ):
882 mock_get_application.return_value = FakeApplication()
883
884 actions = self.loop.run_until_complete(self.libjuju.get_actions("app", "model"))
885
886 self.assertEqual(actions, ["existing_action"])
887
888 mock_get_controller.assert_called_once()
889 mock_get_model.assert_called_once()
890 mock_disconnect_controller.assert_called_once()
891 mock_disconnect_model.assert_called_once()
892
893
894 @asynctest.mock.patch("n2vc.libjuju.Libjuju.get_controller")
895 @asynctest.mock.patch("n2vc.libjuju.Libjuju.get_model")
896 @asynctest.mock.patch("n2vc.libjuju.Libjuju.disconnect_model")
897 @asynctest.mock.patch("n2vc.libjuju.Libjuju.disconnect_controller")
898 @asynctest.mock.patch("juju.application.Application.get_metrics")
899 @asynctest.mock.patch("n2vc.libjuju.Libjuju._get_application")
900 class GetMetricsTest(LibjujuTestCase):
901 def setUp(self):
902 super(GetMetricsTest, self).setUp()
903
904 def test_get_metrics_success(
905 self,
906 mock_get_application,
907 mock_get_metrics,
908 mock_disconnect_controller,
909 mock_disconnect_model,
910 mock_get_model,
911 mock_get_controller,
912 ):
913 mock_get_application.return_value = FakeApplication()
914 mock_get_model.return_value = juju.model.Model()
915
916 self.loop.run_until_complete(self.libjuju.get_metrics("model", "app1"))
917
918 mock_disconnect_controller.assert_called_once()
919 mock_disconnect_model.assert_called_once()
920
921 def test_get_metrics_exception(
922 self,
923 mock_get_application,
924 mock_get_metrics,
925 mock_disconnect_controller,
926 mock_disconnect_model,
927 mock_get_model,
928 mock_get_controller,
929 ):
930 mock_get_model.return_value = juju.model.Model()
931 mock_get_metrics.side_effect = Exception()
932 with self.assertRaises(Exception):
933 self.loop.run_until_complete(self.libjuju.get_metrics("model", "app1"))
934
935 mock_disconnect_controller.assert_called_once()
936 mock_disconnect_model.assert_called_once()
937
938 def test_missing_args_exception(
939 self,
940 mock_get_application,
941 mock_get_metrics,
942 mock_disconnect_controller,
943 mock_disconnect_model,
944 mock_get_model,
945 mock_get_controller,
946 ):
947 mock_get_model.return_value = juju.model.Model()
948
949 with self.assertRaises(Exception):
950 self.loop.run_until_complete(self.libjuju.get_metrics("", ""))
951
952 mock_get_controller.assert_not_called()
953 mock_get_model.assert_not_called()
954 mock_disconnect_controller.assert_not_called()
955 mock_disconnect_model.assert_not_called()
956
957
958 @asynctest.mock.patch("n2vc.libjuju.Libjuju.get_controller")
959 @asynctest.mock.patch("n2vc.libjuju.Libjuju.get_model")
960 @asynctest.mock.patch("n2vc.libjuju.Libjuju.disconnect_model")
961 @asynctest.mock.patch("n2vc.libjuju.Libjuju.disconnect_controller")
962 @asynctest.mock.patch("juju.model.Model.add_relation")
963 class AddRelationTest(LibjujuTestCase):
964 def setUp(self):
965 super(AddRelationTest, self).setUp()
966
967 @asynctest.mock.patch("logging.Logger.warning")
968 def test_not_found(
969 self,
970 mock_warning,
971 mock_add_relation,
972 mock_disconnect_controller,
973 mock_disconnect_model,
974 mock_get_model,
975 mock_get_controller,
976 ):
977 # TODO in libjuju.py should this fail only with a log message?
978 result = {"error": "not found", "response": "response", "request-id": 1}
979
980 mock_get_model.return_value = juju.model.Model()
981 mock_add_relation.side_effect = JujuAPIError(result)
982
983 self.loop.run_until_complete(
984 self.libjuju.add_relation(
985 "model",
986 "app1:relation1",
987 "app2:relation2",
988 )
989 )
990
991 mock_warning.assert_called_with("Relation not found: not found")
992 mock_disconnect_controller.assert_called_once()
993 mock_disconnect_model.assert_called_once()
994
995 @asynctest.mock.patch("logging.Logger.warning")
996 def test_already_exists(
997 self,
998 mock_warning,
999 mock_add_relation,
1000 mock_disconnect_controller,
1001 mock_disconnect_model,
1002 mock_get_model,
1003 mock_get_controller,
1004 ):
1005 # TODO in libjuju.py should this fail silently?
1006 result = {"error": "already exists", "response": "response", "request-id": 1}
1007
1008 mock_get_model.return_value = juju.model.Model()
1009 mock_add_relation.side_effect = JujuAPIError(result)
1010
1011 self.loop.run_until_complete(
1012 self.libjuju.add_relation(
1013 "model",
1014 "app1:relation1",
1015 "app2:relation2",
1016 )
1017 )
1018
1019 mock_warning.assert_called_with("Relation already exists: already exists")
1020 mock_disconnect_controller.assert_called_once()
1021 mock_disconnect_model.assert_called_once()
1022
1023 def test_exception(
1024 self,
1025 mock_add_relation,
1026 mock_disconnect_controller,
1027 mock_disconnect_model,
1028 mock_get_model,
1029 mock_get_controller,
1030 ):
1031 mock_get_model.return_value = juju.model.Model()
1032 result = {"error": "", "response": "response", "request-id": 1}
1033 mock_add_relation.side_effect = JujuAPIError(result)
1034
1035 with self.assertRaises(JujuAPIError):
1036 self.loop.run_until_complete(
1037 self.libjuju.add_relation(
1038 "model",
1039 "app1:relation1",
1040 "app2:relation2",
1041 )
1042 )
1043
1044 mock_disconnect_controller.assert_called_once()
1045 mock_disconnect_model.assert_called_once()
1046
1047 def test_success(
1048 self,
1049 mock_add_relation,
1050 mock_disconnect_controller,
1051 mock_disconnect_model,
1052 mock_get_model,
1053 mock_get_controller,
1054 ):
1055 mock_get_model.return_value = juju.model.Model()
1056
1057 self.loop.run_until_complete(
1058 self.libjuju.add_relation(
1059 "model",
1060 "app1:relation1",
1061 "app2:relation2",
1062 )
1063 )
1064
1065 mock_add_relation.assert_called_with("app1:relation1", "app2:relation2")
1066 mock_disconnect_controller.assert_called_once()
1067 mock_disconnect_model.assert_called_once()
1068
1069 def test_saas(
1070 self,
1071 mock_add_relation,
1072 mock_disconnect_controller,
1073 mock_disconnect_model,
1074 mock_get_model,
1075 mock_get_controller,
1076 ):
1077 mock_get_model.return_value = juju.model.Model()
1078
1079 self.loop.run_until_complete(
1080 self.libjuju.add_relation(
1081 "model",
1082 "app1:relation1",
1083 "saas_name",
1084 )
1085 )
1086
1087 mock_add_relation.assert_called_with("app1:relation1", "saas_name")
1088 mock_disconnect_controller.assert_called_once()
1089 mock_disconnect_model.assert_called_once()
1090
1091
1092 # TODO destroy_model testcase
1093
1094
1095 @asynctest.mock.patch("n2vc.libjuju.Libjuju.get_controller")
1096 @asynctest.mock.patch("n2vc.libjuju.Libjuju.get_model")
1097 @asynctest.mock.patch("n2vc.libjuju.Libjuju.disconnect_controller")
1098 @asynctest.mock.patch("n2vc.libjuju.Libjuju._get_application")
1099 @asynctest.mock.patch("n2vc.libjuju.Libjuju.disconnect_model")
1100 class DestroyApplicationTest(LibjujuTestCase):
1101 def setUp(self):
1102 super(DestroyApplicationTest, self).setUp()
1103
1104 def test_success(
1105 self,
1106 mock_get_controller,
1107 mock_get_model,
1108 mock_disconnect_controller,
1109 mock_get_application,
1110 mock_disconnect_model,
1111 ):
1112 mock_get_application.return_value = FakeApplication()
1113 mock_get_model.return_value = None
1114 self.loop.run_until_complete(
1115 self.libjuju.destroy_application(
1116 "existing_model",
1117 "existing_app",
1118 3600,
1119 )
1120 )
1121 mock_get_application.assert_called()
1122 mock_disconnect_controller.assert_called_once()
1123 mock_disconnect_model.assert_called_once()
1124
1125 def test_no_application(
1126 self,
1127 mock_get_controller,
1128 mock_get_model,
1129 mock_disconnect_controller,
1130 mock_get_application,
1131 mock_disconnect_model,
1132 ):
1133 mock_get_model.return_value = None
1134 mock_get_application.return_value = None
1135
1136 self.loop.run_until_complete(
1137 self.libjuju.destroy_application(
1138 "existing_model",
1139 "existing_app",
1140 3600,
1141 )
1142 )
1143 mock_get_application.assert_called()
1144
1145 def test_exception(
1146 self,
1147 mock_get_controller,
1148 mock_get_model,
1149 mock_disconnect_controller,
1150 mock_get_application,
1151 mock_disconnect_model,
1152 ):
1153 mock_get_application.return_value = FakeApplication
1154 mock_get_model.return_value = None
1155
1156 with self.assertRaises(Exception):
1157 self.loop.run_until_complete(
1158 self.libjuju.destroy_application(
1159 "existing_model",
1160 "existing_app",
1161 0,
1162 )
1163 )
1164 mock_get_application.assert_called_once()
1165
1166
1167 # @asynctest.mock.patch("juju.model.Model.get_machines")
1168 # @asynctest.mock.patch("logging.Logger.debug")
1169 # class DestroyMachineTest(LibjujuTestCase):
1170 # def setUp(self):
1171 # super(DestroyMachineTest, self).setUp()
1172
1173 # def test_success_manual_machine(
1174 # self, mock_debug, mock_get_machines,
1175 # ):
1176 # mock_get_machines.side_effect = [
1177 # {"machine": FakeManualMachine()},
1178 # {"machine": FakeManualMachine()},
1179 # {},
1180 # ]
1181 # self.loop.run_until_complete(
1182 # self.libjuju.destroy_machine(juju.model.Model(), "machine", 2,)
1183 # )
1184 # calls = [
1185 # asynctest.call("Waiting for machine machine is destroyed"),
1186 # asynctest.call("Machine destroyed: machine"),
1187 # ]
1188 # mock_debug.assert_has_calls(calls)
1189
1190 # def test_no_machine(
1191 # self, mock_debug, mock_get_machines,
1192 # ):
1193 # mock_get_machines.return_value = {}
1194 # self.loop.run_until_complete(
1195 # self.libjuju.destroy_machine(juju.model.Model(), "machine", 2)
1196 # )
1197 # mock_debug.assert_called_with("Machine not found: machine")
1198
1199
1200 @asynctest.mock.patch("n2vc.libjuju.Libjuju.get_controller")
1201 @asynctest.mock.patch("n2vc.libjuju.Libjuju.get_model")
1202 @asynctest.mock.patch("n2vc.libjuju.Libjuju.disconnect_model")
1203 @asynctest.mock.patch("n2vc.libjuju.Libjuju.disconnect_controller")
1204 @asynctest.mock.patch("n2vc.libjuju.Libjuju._get_application")
1205 class ConfigureApplicationTest(LibjujuTestCase):
1206 def setUp(self):
1207 super(ConfigureApplicationTest, self).setUp()
1208
1209 def test_success(
1210 self,
1211 mock_get_application,
1212 mock_disconnect_controller,
1213 mock_disconnect_model,
1214 mock_get_model,
1215 mock_get_controller,
1216 ):
1217
1218 mock_get_application.return_value = FakeApplication()
1219
1220 self.loop.run_until_complete(
1221 self.libjuju.configure_application(
1222 "model",
1223 "app",
1224 {"config"},
1225 )
1226 )
1227 mock_get_application.assert_called_once()
1228 mock_disconnect_controller.assert_called_once()
1229 mock_disconnect_model.assert_called_once()
1230
1231 def test_exception(
1232 self,
1233 mock_get_application,
1234 mock_disconnect_controller,
1235 mock_disconnect_model,
1236 mock_get_model,
1237 mock_get_controller,
1238 ):
1239
1240 mock_get_application.side_effect = Exception()
1241
1242 with self.assertRaises(Exception):
1243 self.loop.run_until_complete(
1244 self.libjuju.configure_application(
1245 "model",
1246 "app",
1247 {"config"},
1248 )
1249 )
1250 mock_disconnect_controller.assert_called_once()
1251 mock_disconnect_model.assert_called_once()
1252
1253 def test_controller_exception(
1254 self,
1255 mock_get_application,
1256 mock_disconnect_controller,
1257 mock_disconnect_model,
1258 mock_get_model,
1259 mock_get_controller,
1260 ):
1261
1262 result = {"error": "not found", "response": "response", "request-id": 1}
1263
1264 mock_get_controller.side_effect = JujuAPIError(result)
1265
1266 with self.assertRaises(JujuAPIError):
1267 self.loop.run_until_complete(
1268 self.libjuju.configure_application(
1269 "model",
1270 "app",
1271 {"config"},
1272 )
1273 )
1274 mock_get_model.assert_not_called()
1275 mock_disconnect_controller.assert_not_called()
1276 mock_disconnect_model.assert_not_called()
1277
1278 def test_get_model_exception(
1279 self,
1280 mock_get_application,
1281 mock_disconnect_controller,
1282 mock_disconnect_model,
1283 mock_get_model,
1284 mock_get_controller,
1285 ):
1286
1287 result = {"error": "not found", "response": "response", "request-id": 1}
1288 mock_get_model.side_effect = JujuAPIError(result)
1289
1290 with self.assertRaises(JujuAPIError):
1291 self.loop.run_until_complete(
1292 self.libjuju.configure_application(
1293 "model",
1294 "app",
1295 {"config"},
1296 )
1297 )
1298 mock_get_model.assert_called_once()
1299 mock_disconnect_controller.assert_called_once()
1300 mock_disconnect_model.assert_not_called()
1301
1302
1303 # TODO _get_api_endpoints_db test case
1304 # TODO _update_api_endpoints_db test case
1305 # TODO healthcheck test case
1306
1307
1308 @asynctest.mock.patch("n2vc.libjuju.Libjuju.get_controller")
1309 @asynctest.mock.patch("n2vc.libjuju.Libjuju.disconnect_controller")
1310 @asynctest.mock.patch("juju.controller.Controller.list_models")
1311 class ListModelsTest(LibjujuTestCase):
1312 def setUp(self):
1313 super(ListModelsTest, self).setUp()
1314
1315 def test_containing(
1316 self,
1317 mock_list_models,
1318 mock_disconnect_controller,
1319 mock_get_controller,
1320 ):
1321 mock_get_controller.return_value = juju.controller.Controller()
1322 mock_list_models.return_value = ["existingmodel"]
1323 models = self.loop.run_until_complete(self.libjuju.list_models("existing"))
1324
1325 mock_disconnect_controller.assert_called_once()
1326 self.assertEquals(models, ["existingmodel"])
1327
1328 def test_not_containing(
1329 self,
1330 mock_list_models,
1331 mock_disconnect_controller,
1332 mock_get_controller,
1333 ):
1334 mock_get_controller.return_value = juju.controller.Controller()
1335 mock_list_models.return_value = ["existingmodel", "model"]
1336 models = self.loop.run_until_complete(self.libjuju.list_models("mdl"))
1337
1338 mock_disconnect_controller.assert_called_once()
1339 self.assertEquals(models, [])
1340
1341 def test_no_contains_arg(
1342 self,
1343 mock_list_models,
1344 mock_disconnect_controller,
1345 mock_get_controller,
1346 ):
1347 mock_get_controller.return_value = juju.controller.Controller()
1348 mock_list_models.return_value = ["existingmodel", "model"]
1349 models = self.loop.run_until_complete(self.libjuju.list_models())
1350
1351 mock_disconnect_controller.assert_called_once()
1352 self.assertEquals(models, ["existingmodel", "model"])
1353
1354
1355 @asynctest.mock.patch("n2vc.libjuju.Libjuju.list_models")
1356 class ModelsExistTest(LibjujuTestCase):
1357 def setUp(self):
1358 super(ModelsExistTest, self).setUp()
1359
1360 def test_model_names_none(self, mock_list_models):
1361 mock_list_models.return_value = []
1362 with self.assertRaises(Exception):
1363 self.loop.run_until_complete(self.libjuju.models_exist(None))
1364
1365 def test_model_names_empty(self, mock_list_models):
1366 mock_list_models.return_value = []
1367 with self.assertRaises(Exception):
1368 (exist, non_existing_models) = self.loop.run_until_complete(
1369 self.libjuju.models_exist([])
1370 )
1371
1372 def test_model_names_not_existing(self, mock_list_models):
1373 mock_list_models.return_value = ["prometheus", "grafana"]
1374 (exist, non_existing_models) = self.loop.run_until_complete(
1375 self.libjuju.models_exist(["prometheus2", "grafana"])
1376 )
1377 self.assertFalse(exist)
1378 self.assertEqual(non_existing_models, ["prometheus2"])
1379
1380 def test_model_names_exist(self, mock_list_models):
1381 mock_list_models.return_value = ["prometheus", "grafana"]
1382 (exist, non_existing_models) = self.loop.run_until_complete(
1383 self.libjuju.models_exist(["prometheus", "grafana"])
1384 )
1385 self.assertTrue(exist)
1386 self.assertEqual(non_existing_models, [])
1387
1388
1389 @asynctest.mock.patch("n2vc.libjuju.Libjuju.get_controller")
1390 @asynctest.mock.patch("n2vc.libjuju.Libjuju.disconnect_controller")
1391 @asynctest.mock.patch("juju.controller.Controller.list_offers")
1392 class ListOffers(LibjujuTestCase):
1393 def setUp(self):
1394 super(ListOffers, self).setUp()
1395
1396 def test_disconnect_controller(
1397 self,
1398 mock_list_offers,
1399 mock_disconnect_controller,
1400 mock_get_controller,
1401 ):
1402 mock_get_controller.return_value = juju.controller.Controller()
1403 mock_list_offers.side_effect = Exception()
1404 with self.assertRaises(Exception):
1405 self.loop.run_until_complete(self.libjuju.list_offers("model"))
1406 mock_disconnect_controller.assert_called_once()
1407
1408 def test_empty_list(
1409 self,
1410 mock_list_offers,
1411 mock_disconnect_controller,
1412 mock_get_controller,
1413 ):
1414 mock_get_controller.return_value = juju.controller.Controller()
1415 mock_list_offers.return_value = []
1416 offers = self.loop.run_until_complete(self.libjuju.list_offers("model"))
1417 self.assertEqual(offers, [])
1418 mock_disconnect_controller.assert_called_once()
1419
1420 def test_non_empty_list(
1421 self,
1422 mock_list_offers,
1423 mock_disconnect_controller,
1424 mock_get_controller,
1425 ):
1426 mock_get_controller.return_value = juju.controller.Controller()
1427 mock_list_offers.return_value = ["offer"]
1428 offers = self.loop.run_until_complete(self.libjuju.list_offers("model"))
1429 self.assertEqual(offers, ["offer"])
1430 mock_disconnect_controller.assert_called_once()
1431
1432
1433 @asynctest.mock.patch("n2vc.libjuju.Libjuju.get_controller")
1434 @asynctest.mock.patch("juju.controller.Controller.get_model")
1435 @asynctest.mock.patch("n2vc.libjuju.Libjuju.disconnect_model")
1436 @asynctest.mock.patch("n2vc.libjuju.Libjuju.disconnect_controller")
1437 @asynctest.mock.patch("juju.model.Model.consume")
1438 class ConsumeTest(LibjujuTestCase):
1439 def setUp(self):
1440 super(ConsumeTest, self).setUp()
1441
1442 def test_consume(
1443 self,
1444 mock_consume,
1445 mock_disconnect_controller,
1446 mock_disconnect_model,
1447 mock_get_model,
1448 mock_get_controller,
1449 ):
1450 mock_get_controller.return_value = juju.controller.Controller()
1451 mock_get_model.return_value = juju.model.Model()
1452
1453 self.loop.run_until_complete(self.libjuju.consume("offer_url", "model_name"))
1454 mock_consume.assert_called_once()
1455 mock_disconnect_model.assert_called_once()
1456 mock_disconnect_controller.assert_called_once()
1457
1458 def test_parsing_error_exception(
1459 self,
1460 mock_consume,
1461 mock_disconnect_controller,
1462 mock_disconnect_model,
1463 mock_get_model,
1464 mock_get_controller,
1465 ):
1466 mock_get_controller.return_value = juju.controller.Controller()
1467 mock_get_model.return_value = juju.model.Model()
1468 mock_consume.side_effect = juju.offerendpoints.ParseError("")
1469
1470 with self.assertRaises(juju.offerendpoints.ParseError):
1471 self.loop.run_until_complete(
1472 self.libjuju.consume("offer_url", "model_name")
1473 )
1474 mock_consume.assert_called_once()
1475 mock_disconnect_model.assert_called_once()
1476 mock_disconnect_controller.assert_called_once()
1477
1478 def test_juju_error_exception(
1479 self,
1480 mock_consume,
1481 mock_disconnect_controller,
1482 mock_disconnect_model,
1483 mock_get_model,
1484 mock_get_controller,
1485 ):
1486 mock_get_controller.return_value = juju.controller.Controller()
1487 mock_get_model.return_value = juju.model.Model()
1488 mock_consume.side_effect = juju.errors.JujuError("")
1489
1490 with self.assertRaises(juju.errors.JujuError):
1491 self.loop.run_until_complete(
1492 self.libjuju.consume("offer_url", "model_name")
1493 )
1494 mock_consume.assert_called_once()
1495 mock_disconnect_model.assert_called_once()
1496 mock_disconnect_controller.assert_called_once()
1497
1498 def test_juju_api_error_exception(
1499 self,
1500 mock_consume,
1501 mock_disconnect_controller,
1502 mock_disconnect_model,
1503 mock_get_model,
1504 mock_get_controller,
1505 ):
1506 mock_get_controller.return_value = juju.controller.Controller()
1507 mock_get_model.return_value = juju.model.Model()
1508 mock_consume.side_effect = juju.errors.JujuAPIError(
1509 {"error": "", "response": "", "request-id": ""}
1510 )
1511
1512 with self.assertRaises(juju.errors.JujuAPIError):
1513 self.loop.run_until_complete(
1514 self.libjuju.consume("offer_url", "model_name")
1515 )
1516 mock_consume.assert_called_once()
1517 mock_disconnect_model.assert_called_once()
1518 mock_disconnect_controller.assert_called_once()
1519
1520
1521 @asynctest.mock.patch("n2vc.libjuju.Libjuju.get_k8s_cloud_credential")
1522 @asynctest.mock.patch("n2vc.libjuju.Libjuju.add_cloud")
1523 class AddK8sTest(LibjujuTestCase):
1524 def setUp(self):
1525 super(AddK8sTest, self).setUp()
1526 name = "cloud"
1527 rbac_id = generate_rbac_id()
1528 token = "token"
1529 client_cert_data = "cert"
1530 configuration = kubernetes.client.configuration.Configuration()
1531 storage_class = "storage_class"
1532 credential_name = name
1533
1534 self._add_k8s_args = {
1535 "name": name,
1536 "rbac_id": rbac_id,
1537 "token": token,
1538 "client_cert_data": client_cert_data,
1539 "configuration": configuration,
1540 "storage_class": storage_class,
1541 "credential_name": credential_name,
1542 }
1543
1544 def test_add_k8s(self, mock_add_cloud, mock_get_k8s_cloud_credential):
1545 self.loop.run_until_complete(self.libjuju.add_k8s(**self._add_k8s_args))
1546 mock_add_cloud.assert_called_once()
1547 mock_get_k8s_cloud_credential.assert_called_once()
1548
1549 def test_add_k8s_exception(self, mock_add_cloud, mock_get_k8s_cloud_credential):
1550 mock_add_cloud.side_effect = Exception()
1551 with self.assertRaises(Exception):
1552 self.loop.run_until_complete(self.libjuju.add_k8s(**self._add_k8s_args))
1553 mock_add_cloud.assert_called_once()
1554 mock_get_k8s_cloud_credential.assert_called_once()
1555
1556 def test_add_k8s_missing_name(self, mock_add_cloud, mock_get_k8s_cloud_credential):
1557 self._add_k8s_args["name"] = ""
1558 with self.assertRaises(Exception):
1559 self.loop.run_until_complete(self.libjuju.add_k8s(**self._add_k8s_args))
1560 mock_add_cloud.assert_not_called()
1561
1562 def test_add_k8s_missing_storage_name(
1563 self, mock_add_cloud, mock_get_k8s_cloud_credential
1564 ):
1565 self._add_k8s_args["storage_class"] = ""
1566 with self.assertRaises(Exception):
1567 self.loop.run_until_complete(self.libjuju.add_k8s(**self._add_k8s_args))
1568 mock_add_cloud.assert_not_called()
1569
1570 def test_add_k8s_missing_configuration_keys(
1571 self, mock_add_cloud, mock_get_k8s_cloud_credential
1572 ):
1573 self._add_k8s_args["configuration"] = None
1574 with self.assertRaises(Exception):
1575 self.loop.run_until_complete(self.libjuju.add_k8s(**self._add_k8s_args))
1576 mock_add_cloud.assert_not_called()
1577
1578
1579 @asynctest.mock.patch("n2vc.libjuju.Libjuju.get_controller")
1580 @asynctest.mock.patch("n2vc.libjuju.Libjuju.disconnect_controller")
1581 @asynctest.mock.patch("juju.controller.Controller.add_cloud")
1582 @asynctest.mock.patch("juju.controller.Controller.add_credential")
1583 class AddCloudTest(LibjujuTestCase):
1584 def setUp(self):
1585 super(AddCloudTest, self).setUp()
1586 self.cloud = juju.client.client.Cloud()
1587 self.credential = juju.client.client.CloudCredential()
1588
1589 def test_add_cloud_with_credential(
1590 self,
1591 mock_add_credential,
1592 mock_add_cloud,
1593 mock_disconnect_controller,
1594 mock_get_controller,
1595 ):
1596 mock_get_controller.return_value = juju.controller.Controller()
1597
1598 cloud = self.loop.run_until_complete(
1599 self.libjuju.add_cloud("cloud", self.cloud, credential=self.credential)
1600 )
1601 self.assertEqual(cloud, self.cloud)
1602 mock_add_cloud.assert_called_once_with("cloud", self.cloud)
1603 mock_add_credential.assert_called_once_with(
1604 "cloud", credential=self.credential, cloud="cloud"
1605 )
1606 mock_disconnect_controller.assert_called_once()
1607
1608 def test_add_cloud_no_credential(
1609 self,
1610 mock_add_credential,
1611 mock_add_cloud,
1612 mock_disconnect_controller,
1613 mock_get_controller,
1614 ):
1615 mock_get_controller.return_value = juju.controller.Controller()
1616
1617 cloud = self.loop.run_until_complete(
1618 self.libjuju.add_cloud("cloud", self.cloud)
1619 )
1620 self.assertEqual(cloud, self.cloud)
1621 mock_add_cloud.assert_called_once_with("cloud", self.cloud)
1622 mock_add_credential.assert_not_called()
1623 mock_disconnect_controller.assert_called_once()
1624
1625 def test_add_cloud_exception(
1626 self,
1627 mock_add_credential,
1628 mock_add_cloud,
1629 mock_disconnect_controller,
1630 mock_get_controller,
1631 ):
1632 mock_get_controller.return_value = juju.controller.Controller()
1633 mock_add_cloud.side_effect = Exception()
1634 with self.assertRaises(Exception):
1635 self.loop.run_until_complete(
1636 self.libjuju.add_cloud("cloud", self.cloud, credential=self.credential)
1637 )
1638
1639 mock_add_cloud.assert_called_once_with("cloud", self.cloud)
1640 mock_add_credential.assert_not_called()
1641 mock_disconnect_controller.assert_called_once()
1642
1643 def test_add_credential_exception(
1644 self,
1645 mock_add_credential,
1646 mock_add_cloud,
1647 mock_disconnect_controller,
1648 mock_get_controller,
1649 ):
1650 mock_get_controller.return_value = juju.controller.Controller()
1651 mock_add_credential.side_effect = Exception()
1652 with self.assertRaises(Exception):
1653 self.loop.run_until_complete(
1654 self.libjuju.add_cloud("cloud", self.cloud, credential=self.credential)
1655 )
1656
1657 mock_add_cloud.assert_called_once_with("cloud", self.cloud)
1658 mock_add_credential.assert_called_once_with(
1659 "cloud", credential=self.credential, cloud="cloud"
1660 )
1661 mock_disconnect_controller.assert_called_once()
1662
1663
1664 @asynctest.mock.patch("n2vc.libjuju.Libjuju.get_controller")
1665 @asynctest.mock.patch("n2vc.libjuju.Libjuju.disconnect_controller")
1666 @asynctest.mock.patch("juju.controller.Controller.remove_cloud")
1667 class RemoveCloudTest(LibjujuTestCase):
1668 def setUp(self):
1669 super(RemoveCloudTest, self).setUp()
1670
1671 def test_remove_cloud(
1672 self,
1673 mock_remove_cloud,
1674 mock_disconnect_controller,
1675 mock_get_controller,
1676 ):
1677 mock_get_controller.return_value = juju.controller.Controller()
1678
1679 self.loop.run_until_complete(self.libjuju.remove_cloud("cloud"))
1680 mock_remove_cloud.assert_called_once_with("cloud")
1681 mock_disconnect_controller.assert_called_once()
1682
1683 def test_remove_cloud_exception(
1684 self,
1685 mock_remove_cloud,
1686 mock_disconnect_controller,
1687 mock_get_controller,
1688 ):
1689 mock_get_controller.return_value = juju.controller.Controller()
1690 mock_remove_cloud.side_effect = Exception()
1691
1692 with self.assertRaises(Exception):
1693 self.loop.run_until_complete(self.libjuju.remove_cloud("cloud"))
1694 mock_remove_cloud.assert_called_once_with("cloud")
1695 mock_disconnect_controller.assert_called_once()
1696
1697
1698 @asynctest.mock.patch("kubernetes.client.configuration.Configuration")
1699 class GetK8sCloudCredentials(LibjujuTestCase):
1700 def setUp(self):
1701 super(GetK8sCloudCredentials, self).setUp()
1702 self.cert_data = "cert"
1703 self.token = "token"
1704
1705 @asynctest.mock.patch("n2vc.exceptions.JujuInvalidK8sConfiguration")
1706 def test_not_supported(self, mock_exception, mock_configuration):
1707 mock_configuration.username = ""
1708 mock_configuration.password = ""
1709 mock_configuration.ssl_ca_cert = None
1710 mock_configuration.cert_file = None
1711 mock_configuration.key_file = None
1712 exception_raised = False
1713 self.token = None
1714 self.cert_data = None
1715 try:
1716 _ = self.libjuju.get_k8s_cloud_credential(
1717 mock_configuration,
1718 self.cert_data,
1719 self.token,
1720 )
1721 except JujuInvalidK8sConfiguration as e:
1722 exception_raised = True
1723 self.assertEqual(
1724 e.message,
1725 "authentication method not supported",
1726 )
1727 self.assertTrue(exception_raised)
1728
1729 def test_user_pass(self, mock_configuration):
1730 mock_configuration.username = "admin"
1731 mock_configuration.password = "admin"
1732 mock_configuration.ssl_ca_cert = None
1733 mock_configuration.cert_file = None
1734 mock_configuration.key_file = None
1735 self.token = None
1736 self.cert_data = None
1737 credential = self.libjuju.get_k8s_cloud_credential(
1738 mock_configuration,
1739 self.cert_data,
1740 self.token,
1741 )
1742 self.assertEqual(
1743 credential,
1744 juju.client._definitions.CloudCredential(
1745 attrs={"username": "admin", "password": "admin"}, auth_type="userpass"
1746 ),
1747 )
1748
1749 def test_user_pass_with_cert(self, mock_configuration):
1750 mock_configuration.username = "admin"
1751 mock_configuration.password = "admin"
1752 mock_configuration.ssl_ca_cert = None
1753 mock_configuration.cert_file = None
1754 mock_configuration.key_file = None
1755 self.token = None
1756 credential = self.libjuju.get_k8s_cloud_credential(
1757 mock_configuration,
1758 self.cert_data,
1759 self.token,
1760 )
1761 self.assertEqual(
1762 credential,
1763 juju.client._definitions.CloudCredential(
1764 attrs={
1765 "ClientCertificateData": self.cert_data,
1766 "username": "admin",
1767 "password": "admin",
1768 },
1769 auth_type="userpasswithcert",
1770 ),
1771 )
1772
1773 def test_user_no_pass(self, mock_configuration):
1774 mock_configuration.username = "admin"
1775 mock_configuration.password = ""
1776 mock_configuration.ssl_ca_cert = None
1777 mock_configuration.cert_file = None
1778 mock_configuration.key_file = None
1779 self.token = None
1780 self.cert_data = None
1781 with patch.object(self.libjuju.log, "debug") as mock_debug:
1782 credential = self.libjuju.get_k8s_cloud_credential(
1783 mock_configuration,
1784 self.cert_data,
1785 self.token,
1786 )
1787 self.assertEqual(
1788 credential,
1789 juju.client._definitions.CloudCredential(
1790 attrs={"username": "admin", "password": ""}, auth_type="userpass"
1791 ),
1792 )
1793 mock_debug.assert_called_once_with(
1794 "credential for user admin has empty password"
1795 )
1796
1797 def test_cert(self, mock_configuration):
1798 mock_configuration.username = ""
1799 mock_configuration.password = ""
1800 mock_configuration.api_key = {"authorization": "Bearer Token"}
1801 ssl_ca_cert = tempfile.NamedTemporaryFile()
1802 with open(ssl_ca_cert.name, "w") as ssl_ca_cert_file:
1803 ssl_ca_cert_file.write("cacert")
1804 mock_configuration.ssl_ca_cert = ssl_ca_cert.name
1805 mock_configuration.cert_file = None
1806 mock_configuration.key_file = None
1807 credential = self.libjuju.get_k8s_cloud_credential(
1808 mock_configuration,
1809 self.cert_data,
1810 self.token,
1811 )
1812 self.assertEqual(
1813 credential,
1814 juju.client._definitions.CloudCredential(
1815 attrs={"ClientCertificateData": self.cert_data, "Token": self.token},
1816 auth_type="certificate",
1817 ),
1818 )
1819
1820 # TODO: Fix this test when oauth authentication is supported
1821 # def test_oauth2(self, mock_configuration):
1822 # mock_configuration.username = ""
1823 # mock_configuration.password = ""
1824 # mock_configuration.api_key = {"authorization": "Bearer Token"}
1825 # key = tempfile.NamedTemporaryFile()
1826 # with open(key.name, "w") as key_file:
1827 # key_file.write("key")
1828 # mock_configuration.ssl_ca_cert = None
1829 # mock_configuration.cert_file = None
1830 # mock_configuration.key_file = key.name
1831 # credential = self.libjuju.get_k8s_cloud_credential(
1832 # mock_configuration,
1833 # self.cert_data,
1834 # self.token,
1835 # )
1836 # self.assertEqual(
1837 # credential,
1838 # juju.client._definitions.CloudCredential(
1839 # attrs={"ClientKeyData": "key", "Token": "Token"},
1840 # auth_type="oauth2",
1841 # ),
1842 # )
1843
1844 # @asynctest.mock.patch("n2vc.exceptions.JujuInvalidK8sConfiguration")
1845 # def test_oauth2_missing_token(self, mock_exception, mock_configuration):
1846 # mock_configuration.username = ""
1847 # mock_configuration.password = ""
1848 # key = tempfile.NamedTemporaryFile()
1849 # with open(key.name, "w") as key_file:
1850 # key_file.write("key")
1851 # mock_configuration.ssl_ca_cert = None
1852 # mock_configuration.cert_file = None
1853 # mock_configuration.key_file = key.name
1854 # exception_raised = False
1855 # try:
1856 # _ = self.libjuju.get_k8s_cloud_credential(
1857 # mock_configuration,
1858 # self.cert_data,
1859 # self.token,
1860 # )
1861 # except JujuInvalidK8sConfiguration as e:
1862 # exception_raised = True
1863 # self.assertEqual(
1864 # e.message,
1865 # "missing token for auth type oauth2",
1866 # )
1867 # self.assertTrue(exception_raised)
1868
1869 def test_exception_cannot_set_token_and_userpass(self, mock_configuration):
1870 mock_configuration.username = "admin"
1871 mock_configuration.password = "pass"
1872 mock_configuration.api_key = {"authorization": "No_bearer_token"}
1873 mock_configuration.ssl_ca_cert = None
1874 mock_configuration.cert_file = None
1875 mock_configuration.key_file = None
1876 exception_raised = False
1877 try:
1878 _ = self.libjuju.get_k8s_cloud_credential(
1879 mock_configuration,
1880 self.cert_data,
1881 self.token,
1882 )
1883 except JujuInvalidK8sConfiguration as e:
1884 exception_raised = True
1885 self.assertEqual(
1886 e.message,
1887 "Cannot set both token and user/pass",
1888 )
1889 self.assertTrue(exception_raised)