X-Git-Url: https://osm.etsi.org/gitweb/?p=osm%2FN2VC.git;a=blobdiff_plain;f=n2vc%2Ftests%2Funit%2Ftest_juju_watcher.py;h=5f8127420649a3c63629ea551a3cb6807dfb53d7;hp=593ff0d997902d61d510509ae108c51207ce51da;hb=HEAD;hpb=c38a696d168531e3c067451044262ef4d78ef11f diff --git a/n2vc/tests/unit/test_juju_watcher.py b/n2vc/tests/unit/test_juju_watcher.py index 593ff0d..b9e9e36 100644 --- a/n2vc/tests/unit/test_juju_watcher.py +++ b/n2vc/tests/unit/test_juju_watcher.py @@ -12,19 +12,24 @@ # See the License for the specific language governing permissions and # limitations under the License. +import json +import os +from time import sleep import asynctest import asyncio -from unittest import mock, TestCase -from unittest.mock import Mock from n2vc.juju_watcher import JujuModelWatcher, entity_ready, status from n2vc.exceptions import EntityInvalidException from .utils import FakeN2VC, AsyncMock, Deltas, FakeWatcher from juju.application import Application -from juju.model import Model +from juju.action import Action from juju.annotation import Annotation +from juju.client._definitions import AllWatcherNextResults from juju.machine import Machine -from juju.action import Action +from juju.model import Model +from juju.unit import Unit +from unittest import mock, TestCase +from unittest.mock import Mock class JujuWatcherTest(asynctest.TestCase): @@ -45,6 +50,7 @@ class JujuWatcherTest(asynctest.TestCase): def test_model_watcher(self, allwatcher): tests = Deltas allwatcher.return_value = FakeWatcher() + n2vc = AsyncMock() for test in tests: with self.assertRaises(asyncio.TimeoutError): allwatcher.return_value.delta_to_return = [test.delta] @@ -55,12 +61,12 @@ class JujuWatcherTest(asynctest.TestCase): test.filter.entity_type, timeout=0, db_dict={"something"}, - n2vc=self.n2vc, + n2vc=n2vc, + vca_id=None, ) ) - self.assertEqual(self.n2vc.last_written_values, test.db.data) - self.n2vc.last_written_values = None + n2vc.write_app_status_to_db.assert_called() @mock.patch("n2vc.juju_watcher.asyncio.wait") def test_wait_for(self, wait): @@ -121,6 +127,167 @@ class EntityReadyTest(TestCase): self.assertTrue(isinstance(entity_ready(entity), bool)) +@mock.patch("n2vc.juju_watcher.client.AllWatcherFacade.from_connection") +class EntityStateTest(TestCase): + def setUp(self): + self.model = Model() + self.model._connector = mock.MagicMock() + self.loop = asyncio.new_event_loop() + self.application = Mock(Application) + self.upgrade_file = None + self.line_number = 1 + + def _fetch_next_delta(self): + delta = None + while delta is None: + raw_data = self.upgrade_file.readline() + if not raw_data: + raise EOFError("Log file is out of events") + try: + delta = json.loads(raw_data) + except ValueError: + continue + + if delta[0] == "unit": + if delta[2]["life"] == "dead": + # Remove the unit from the application + for unit in self.application.units: + if unit.entity_id == delta[2]["name"]: + self.application.units.remove(unit) + else: + unit_present = False + for unit in self.application.units: + if unit.entity_id == delta[2]["name"]: + unit_present = True + + if not unit_present: + print("Application gets a new unit: {}".format(delta[2]["name"])) + unit = Mock(Unit) + unit.entity_id = delta[2]["name"] + unit.entity_type = "unit" + self.application.units.append(unit) + + print("{} {}".format(self.line_number, delta)) + self.line_number = self.line_number + 1 + + return AllWatcherNextResults( + deltas=[ + delta, + ] + ) + + def _ensure_state(self, filename, mock_all_watcher): + with open( + os.path.join(os.path.dirname(__file__), "testdata", filename), + "r", + ) as self.upgrade_file: + all_changes = AsyncMock() + all_changes.Next.side_effect = self._fetch_next_delta + mock_all_watcher.return_value = all_changes + + self.loop.run_until_complete( + JujuModelWatcher.ensure_units_idle( + model=self.model, application=self.application + ) + ) + + with self.assertRaises(EOFError, msg="Not all events consumed"): + change = self._fetch_next_delta() + print(change.deltas[0].deltas) + + def _slow_changes(self): + sleep(0.1) + return AllWatcherNextResults( + deltas=[ + json.loads( + """["unit","change", + { + "name": "app-vnf-7a49ace2b6-z0/2", + "application": "app-vnf-7a49ace2b6-z0", + "workload-status": { + "current": "active", + "message": "", + "since": "2022-04-26T18:50:27.579802723Z"}, + "agent-status": { + "current": "idle", + "message": "", + "since": "2022-04-26T18:50:28.592142816Z"} + }]""" + ), + ] + ) + + def test_timeout(self, mock_all_watcher): + unit1 = Mock(Unit) + unit1.entity_id = "app-vnf-7a49ace2b6-z0/0" + unit1.entity_type = "unit" + self.application.units = [ + unit1, + ] + + all_changes = AsyncMock() + all_changes.Next.side_effect = self._slow_changes + mock_all_watcher.return_value = all_changes + + with self.assertRaises(TimeoutError): + self.loop.run_until_complete( + JujuModelWatcher.wait_for_units_idle( + model=self.model, application=self.application, timeout=0.01 + ) + ) + + def test_machine_unit_upgrade(self, mock_all_watcher): + unit1 = Mock(Unit) + unit1.entity_id = "app-vnf-7a49ace2b6-z0/0" + unit1.entity_type = "unit" + unit2 = Mock(Unit) + unit2.entity_id = "app-vnf-7a49ace2b6-z0/1" + unit2.entity_type = "unit" + unit3 = Mock(Unit) + unit3.entity_id = "app-vnf-7a49ace2b6-z0/2" + unit3.entity_type = "unit" + + self.application.units = [unit1, unit2, unit3] + + self._ensure_state("upgrade-machine.log", mock_all_watcher) + + def test_operator_upgrade(self, mock_all_watcher): + unit1 = Mock(Unit) + unit1.entity_id = "sshproxy/0" + unit1.entity_type = "unit" + self.application.units = [ + unit1, + ] + self._ensure_state("upgrade-operator.log", mock_all_watcher) + + def test_podspec_stateful_upgrade(self, mock_all_watcher): + unit1 = Mock(Unit) + unit1.entity_id = "mongodb/0" + unit1.entity_type = "unit" + self.application.units = [ + unit1, + ] + self._ensure_state("upgrade-podspec-stateful.log", mock_all_watcher) + + def test_podspec_stateless_upgrade(self, mock_all_watcher): + unit1 = Mock(Unit) + unit1.entity_id = "lcm/9" + unit1.entity_type = "unit" + self.application.units = [ + unit1, + ] + self._ensure_state("upgrade-podspec-stateless.log", mock_all_watcher) + + def test_sidecar_upgrade(self, mock_all_watcher): + unit1 = Mock(Unit) + unit1.entity_id = "kafka/0" + unit1.entity_type = "unit" + self.application.units = [ + unit1, + ] + self._ensure_state("upgrade-sidecar.log", mock_all_watcher) + + class StatusTest(TestCase): def setUp(self): self.model = Model() @@ -140,3 +307,30 @@ class StatusTest(TestCase): value = status(application) mock_derive_status.assert_called_once() self.assertTrue(isinstance(value, str)) + + +@asynctest.mock.patch("asyncio.sleep") +class WaitForModelTest(asynctest.TestCase): + @asynctest.mock.patch("juju.client.connector.Connector.connect") + def setUp(self, mock_connect=None): + self.loop = asyncio.new_event_loop() + self.model = Model() + + @asynctest.mock.patch("juju.model.Model.block_until") + def test_wait_for_model(self, mock_block_until, mock_sleep): + self.loop.run_until_complete( + JujuModelWatcher.wait_for_model(self.model, timeout=None) + ) + mock_block_until.assert_called() + + @asynctest.mock.patch("asyncio.ensure_future") + @asynctest.mock.patch("asyncio.wait") + def test_wait_for_model_exception(self, mock_wait, mock_ensure_future, mock_sleep): + task = Mock() + mock_ensure_future.return_value = task + mock_wait.side_effect = Exception + with self.assertRaises(Exception): + self.loop.run_until_complete( + JujuModelWatcher.wait_for_model(self.model, timeout=None) + ) + task.cancel.assert_called()