Coverage for n2vc/tests/unit/test_juju_watcher.py: 98%
209 statements
« prev ^ index » next coverage.py v7.6.12, created at 2025-05-07 06:04 +0000
« prev ^ index » next coverage.py v7.6.12, created at 2025-05-07 06:04 +0000
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.
15import json
16import os
17from time import sleep
18import asynctest
19import asyncio
21from n2vc.juju_watcher import JujuModelWatcher, entity_ready, status
22from n2vc.exceptions import EntityInvalidException
23from .utils import FakeN2VC, AsyncMock, Deltas, FakeWatcher
24from juju.application import Application
25from juju.action import Action
26from juju.annotation import Annotation
27from juju.client._definitions import AllWatcherNextResults
28from juju.machine import Machine
29from juju.model import Model
30from juju.unit import Unit
31from unittest import mock, TestCase
32from unittest.mock import Mock
35class JujuWatcherTest(asynctest.TestCase):
36 def setUp(self):
37 self.n2vc = FakeN2VC()
38 self.model = Mock()
39 self.loop = asyncio.new_event_loop()
41 def test_get_status(self):
42 tests = Deltas
43 for test in tests:
44 (status, message, vca_status) = JujuModelWatcher.get_status(test.delta)
45 self.assertEqual(status, test.entity_status.status)
46 self.assertEqual(message, test.entity_status.message)
47 self.assertEqual(vca_status, test.entity_status.vca_status)
49 @mock.patch("n2vc.juju_watcher.client.AllWatcherFacade.from_connection")
50 def test_model_watcher(self, allwatcher):
51 tests = Deltas
52 allwatcher.return_value = FakeWatcher()
53 n2vc = AsyncMock()
54 for test in tests:
55 with self.assertRaises(asyncio.TimeoutError):
56 allwatcher.return_value.delta_to_return = [test.delta]
57 self.loop.run_until_complete(
58 JujuModelWatcher.model_watcher(
59 self.model,
60 test.filter.entity_id,
61 test.filter.entity_type,
62 timeout=0,
63 db_dict={"something"},
64 n2vc=n2vc,
65 vca_id=None,
66 )
67 )
69 n2vc.write_app_status_to_db.assert_called()
71 @mock.patch("n2vc.juju_watcher.asyncio.wait")
72 def test_wait_for(self, wait):
73 wait.return_value = asyncio.Future()
74 wait.return_value.set_result(None)
76 machine = AsyncMock()
77 self.loop.run_until_complete(JujuModelWatcher.wait_for(self.model, machine))
79 @mock.patch("n2vc.juju_watcher.asyncio.wait")
80 def test_wait_for_exception(self, wait):
81 wait.return_value = asyncio.Future()
82 wait.return_value.set_result(None)
83 wait.side_effect = Exception("error")
85 machine = AsyncMock()
86 with self.assertRaises(Exception):
87 self.loop.run_until_complete(JujuModelWatcher.wait_for(self.model, machine))
89 def test_wait_for_invalid_entity_exception(self):
90 with self.assertRaises(EntityInvalidException):
91 self.loop.run_until_complete(
92 JujuModelWatcher.wait_for(
93 self.model,
94 Annotation(0, self.model),
95 total_timeout=None,
96 progress_timeout=None,
97 )
98 )
101class EntityReadyTest(TestCase):
102 @mock.patch("juju.application.Application.units")
103 def setUp(self, mock_units):
104 self.model = Model()
105 self.model._connector = mock.MagicMock()
107 def test_invalid_entity(self):
108 with self.assertRaises(EntityInvalidException):
109 entity_ready(Annotation(0, self.model))
111 @mock.patch("juju.machine.Machine.agent_status")
112 def test_machine_entity(self, mock_machine_agent_status):
113 entity = Machine(0, self.model)
114 self.assertEqual(entity.entity_type, "machine")
115 self.assertTrue(isinstance(entity_ready(entity), bool))
117 @mock.patch("juju.action.Action.status")
118 def test_action_entity(self, mock_action_status):
119 entity = Action(0, self.model)
120 self.assertEqual(entity.entity_type, "action")
121 self.assertTrue(isinstance(entity_ready(entity), bool))
123 @mock.patch("juju.application.Application.status")
124 def test_application_entity(self, mock_application_status):
125 entity = Application(0, self.model)
126 self.assertEqual(entity.entity_type, "application")
127 self.assertTrue(isinstance(entity_ready(entity), bool))
130@mock.patch("n2vc.juju_watcher.client.AllWatcherFacade.from_connection")
131class EntityStateTest(TestCase):
132 def setUp(self):
133 self.model = Model()
134 self.model._connector = mock.MagicMock()
135 self.loop = asyncio.new_event_loop()
136 self.application = Mock(Application)
137 self.upgrade_file = None
138 self.line_number = 1
140 def _fetch_next_delta(self):
141 delta = None
142 while delta is None:
143 raw_data = self.upgrade_file.readline()
144 if not raw_data:
145 raise EOFError("Log file is out of events")
146 try:
147 delta = json.loads(raw_data)
148 except ValueError:
149 continue
151 if delta[0] == "unit":
152 if delta[2]["life"] == "dead":
153 # Remove the unit from the application
154 for unit in self.application.units:
155 if unit.entity_id == delta[2]["name"]:
156 self.application.units.remove(unit)
157 else:
158 unit_present = False
159 for unit in self.application.units:
160 if unit.entity_id == delta[2]["name"]:
161 unit_present = True
163 if not unit_present:
164 print("Application gets a new unit: {}".format(delta[2]["name"]))
165 unit = Mock(Unit)
166 unit.entity_id = delta[2]["name"]
167 unit.entity_type = "unit"
168 self.application.units.append(unit)
170 print("{} {}".format(self.line_number, delta))
171 self.line_number = self.line_number + 1
173 return AllWatcherNextResults(
174 deltas=[
175 delta,
176 ]
177 )
179 def _ensure_state(self, filename, mock_all_watcher):
180 with open(
181 os.path.join(os.path.dirname(__file__), "testdata", filename),
182 "r",
183 ) as self.upgrade_file:
184 all_changes = AsyncMock()
185 all_changes.Next.side_effect = self._fetch_next_delta
186 mock_all_watcher.return_value = all_changes
188 self.loop.run_until_complete(
189 JujuModelWatcher.ensure_units_idle(
190 model=self.model, application=self.application
191 )
192 )
194 with self.assertRaises(EOFError, msg="Not all events consumed"):
195 change = self._fetch_next_delta()
196 print(change.deltas[0].deltas)
198 def _slow_changes(self):
199 sleep(0.1)
200 return AllWatcherNextResults(
201 deltas=[
202 json.loads(
203 """["unit","change",
204 {
205 "name": "app-vnf-7a49ace2b6-z0/2",
206 "application": "app-vnf-7a49ace2b6-z0",
207 "workload-status": {
208 "current": "active",
209 "message": "",
210 "since": "2022-04-26T18:50:27.579802723Z"},
211 "agent-status": {
212 "current": "idle",
213 "message": "",
214 "since": "2022-04-26T18:50:28.592142816Z"}
215 }]"""
216 ),
217 ]
218 )
220 def test_timeout(self, mock_all_watcher):
221 unit1 = Mock(Unit)
222 unit1.entity_id = "app-vnf-7a49ace2b6-z0/0"
223 unit1.entity_type = "unit"
224 self.application.units = [
225 unit1,
226 ]
228 all_changes = AsyncMock()
229 all_changes.Next.side_effect = self._slow_changes
230 mock_all_watcher.return_value = all_changes
232 with self.assertRaises(TimeoutError):
233 self.loop.run_until_complete(
234 JujuModelWatcher.wait_for_units_idle(
235 model=self.model, application=self.application, timeout=0.01
236 )
237 )
239 def test_machine_unit_upgrade(self, mock_all_watcher):
240 unit1 = Mock(Unit)
241 unit1.entity_id = "app-vnf-7a49ace2b6-z0/0"
242 unit1.entity_type = "unit"
243 unit2 = Mock(Unit)
244 unit2.entity_id = "app-vnf-7a49ace2b6-z0/1"
245 unit2.entity_type = "unit"
246 unit3 = Mock(Unit)
247 unit3.entity_id = "app-vnf-7a49ace2b6-z0/2"
248 unit3.entity_type = "unit"
250 self.application.units = [unit1, unit2, unit3]
252 self._ensure_state("upgrade-machine.log", mock_all_watcher)
254 def test_operator_upgrade(self, mock_all_watcher):
255 unit1 = Mock(Unit)
256 unit1.entity_id = "sshproxy/0"
257 unit1.entity_type = "unit"
258 self.application.units = [
259 unit1,
260 ]
261 self._ensure_state("upgrade-operator.log", mock_all_watcher)
263 def test_podspec_stateful_upgrade(self, mock_all_watcher):
264 unit1 = Mock(Unit)
265 unit1.entity_id = "mongodb/0"
266 unit1.entity_type = "unit"
267 self.application.units = [
268 unit1,
269 ]
270 self._ensure_state("upgrade-podspec-stateful.log", mock_all_watcher)
272 def test_podspec_stateless_upgrade(self, mock_all_watcher):
273 unit1 = Mock(Unit)
274 unit1.entity_id = "lcm/9"
275 unit1.entity_type = "unit"
276 self.application.units = [
277 unit1,
278 ]
279 self._ensure_state("upgrade-podspec-stateless.log", mock_all_watcher)
281 def test_sidecar_upgrade(self, mock_all_watcher):
282 unit1 = Mock(Unit)
283 unit1.entity_id = "kafka/0"
284 unit1.entity_type = "unit"
285 self.application.units = [
286 unit1,
287 ]
288 self._ensure_state("upgrade-sidecar.log", mock_all_watcher)
291class StatusTest(TestCase):
292 def setUp(self):
293 self.model = Model()
294 self.model._connector = mock.MagicMock()
296 @mock.patch("n2vc.juju_watcher.derive_status")
297 def test_invalid_entity(self, mock_derive_status):
298 application = mock.MagicMock()
299 mock_derive_status.return_value = "active"
301 class FakeUnit:
302 @property
303 def workload_status(self):
304 return "active"
306 application.units = [FakeUnit()]
307 value = status(application)
308 mock_derive_status.assert_called_once()
309 self.assertTrue(isinstance(value, str))
312@asynctest.mock.patch("asyncio.sleep")
313class WaitForModelTest(asynctest.TestCase):
314 @asynctest.mock.patch("juju.client.connector.Connector.connect")
315 def setUp(self, mock_connect=None):
316 self.loop = asyncio.new_event_loop()
317 self.model = Model()
319 @asynctest.mock.patch("juju.model.Model.block_until")
320 def test_wait_for_model(self, mock_block_until, mock_sleep):
321 self.loop.run_until_complete(
322 JujuModelWatcher.wait_for_model(self.model, timeout=None)
323 )
324 mock_block_until.assert_called()
326 @asynctest.mock.patch("asyncio.ensure_future")
327 @asynctest.mock.patch("asyncio.wait")
328 def test_wait_for_model_exception(self, mock_wait, mock_ensure_future, mock_sleep):
329 task = Mock()
330 mock_ensure_future.return_value = task
331 mock_wait.side_effect = Exception
332 with self.assertRaises(Exception):
333 self.loop.run_until_complete(
334 JujuModelWatcher.wait_for_model(self.model, timeout=None)
335 )
336 task.cancel.assert_called()