From 6fd687cc90688fe0fbd387eb2e8d96ffef4d3cc2 Mon Sep 17 00:00:00 2001 From: HgO Date: Mon, 8 Aug 2022 11:26:08 +0200 Subject: [PATCH] add force option for update silence --- matrix_alertbot/alertmanager.py | 24 +++++++++---- matrix_alertbot/command.py | 1 + tests/test_alertmanager.py | 61 +++++++++++++++++++++++++++++---- tests/test_command.py | 32 +++++++++++------ 4 files changed, 95 insertions(+), 23 deletions(-) diff --git a/matrix_alertbot/alertmanager.py b/matrix_alertbot/alertmanager.py index b8754d0..905f61c 100644 --- a/matrix_alertbot/alertmanager.py +++ b/matrix_alertbot/alertmanager.py @@ -87,6 +87,8 @@ class AlertmanagerClient: fingerprint: str, user: Optional[str] = None, duration_seconds: Optional[int] = None, + *, + force: bool = False, ) -> str: logger.debug( f"Reading silence for alert with fingerprint {fingerprint} from cache" @@ -107,11 +109,12 @@ class AlertmanagerClient: f"Updating silence with ID {silence_id} for alert with fingerprint {fingerprint}" ) - if duration_seconds is None: - if expire_time is not None: - raise SilenceExtendError( - f"Cannot extend silence ID {silence_id} with static duration." - ) + # If silence in cache had a duration, and the new silence doesn't have a duration + # then we cannot update this silence. + if not force and duration_seconds is None and expire_time is not None: + raise SilenceExtendError( + f"Cannot extend silence ID {silence_id} with static duration." + ) silence = await self.get_silence(silence_id) if user is None: @@ -123,10 +126,17 @@ class AlertmanagerClient: ) async def create_or_update_silence( - self, fingerprint: str, user: str, duration_seconds: Optional[int] = None + self, + fingerprint: str, + user: str, + duration_seconds: Optional[int] = None, + *, + force: bool = False, ) -> str: try: - silence_id = await self.update_silence(fingerprint, user, duration_seconds) + silence_id = await self.update_silence( + fingerprint, user, duration_seconds, force=force + ) except SilenceNotFoundError: silence_id = await self.create_silence(fingerprint, user, duration_seconds) return silence_id diff --git a/matrix_alertbot/command.py b/matrix_alertbot/command.py index c34ebc8..545cfbf 100644 --- a/matrix_alertbot/command.py +++ b/matrix_alertbot/command.py @@ -135,6 +135,7 @@ class AckAlertCommand(BaseAlertCommand): alert_fingerprint, self.room.user_name(self.sender), duration_seconds, + force=True ) except AlertNotFoundError as e: logger.warning(f"Unable to create silence: {e}") diff --git a/tests/test_alertmanager.py b/tests/test_alertmanager.py index 9a9b6b6..96c4a0c 100644 --- a/tests/test_alertmanager.py +++ b/tests/test_alertmanager.py @@ -25,7 +25,7 @@ from matrix_alertbot.errors import ( async def update_silence_raise_silence_not_found( - fingerprint: str, user: str, duration_seconds: int + fingerprint: str, user: str, duration_seconds: int, *, force: bool = False ) -> str: raise SilenceNotFoundError @@ -439,7 +439,7 @@ class AlertmanagerClientTestCase(unittest.IsolatedAsyncioTestCase): fake_cache.set.assert_called_once_with("fingerprint1", "silence1", expire=86400) @freeze_time(datetime.utcfromtimestamp(0)) - async def test_update_silence_with_duration(self) -> None: + async def test_update_silence_raise_extend_error(self) -> None: fake_cache = FakeCache() async with FakeAlertmanagerServerWithoutSilence() as fake_alertmanager_server: @@ -455,6 +455,47 @@ class AlertmanagerClientTestCase(unittest.IsolatedAsyncioTestCase): await alertmanager_client.get_silence("silence2") self.assertEqual({"fingerprint1": ("silence1", 86400)}, fake_cache.cache) + @freeze_time(datetime.utcfromtimestamp(0)) + async def test_update_silence_remove_duration(self) -> None: + fake_cache = FakeCache() + + async with FakeAlertmanagerServerWithoutSilence() as fake_alertmanager_server: + port = fake_alertmanager_server.port + alertmanager_client = AlertmanagerClient( + f"http://localhost:{port}", fake_cache + ) + async with aiotools.closing_async(alertmanager_client): + silence_id1 = await alertmanager_client.create_silence( + "fingerprint1", "user", 86400 + ) + silence_id2 = await alertmanager_client.update_silence( + "fingerprint1", force=True + ) + silence2 = await alertmanager_client.get_silence("silence2") + + self.assertEqual("silence1", silence_id1) + self.assertEqual("silence2", silence_id2) + self.assertEqual( + { + "id": "silence2", + "status": {"state": "active"}, + "matchers": [ + { + "name": "alertname", + "value": "alert1", + "isRegex": False, + "isEqual": True, + } + ], + "createdBy": "user", + "startsAt": "1970-01-01T00:00:00", + "endsAt": "1970-01-01T03:00:00", + "comment": "Acknowledge alert from Matrix", + }, + silence2, + ) + self.assertEqual({"fingerprint1": ("silence2", None)}, fake_cache.cache) + @freeze_time(datetime.utcfromtimestamp(0)) async def test_update_silence_override_user_and_duration(self) -> None: fake_cache = FakeCache() @@ -509,7 +550,9 @@ class AlertmanagerClientTestCase(unittest.IsolatedAsyncioTestCase): ) self.assertEqual("silence1", silence_id1) - fake_update_silence.assert_called_once_with("fingerprint1", "user", 86400) + fake_update_silence.assert_called_once_with( + "fingerprint1", "user", 86400, force=False + ) fake_create_silence.assert_called_once_with("fingerprint1", "user", 86400) @patch.object(matrix_alertbot.alertmanager.AlertmanagerClient, "update_silence") @@ -527,7 +570,9 @@ class AlertmanagerClientTestCase(unittest.IsolatedAsyncioTestCase): ) self.assertEqual("silence1", silence_id1) - fake_update_silence.assert_called_once_with("fingerprint1", "user", 86400) + fake_update_silence.assert_called_once_with( + "fingerprint1", "user", 86400, force=False + ) fake_create_silence.assert_not_called() @freeze_time(datetime.utcfromtimestamp(0)) @@ -623,7 +668,9 @@ class AlertmanagerClientTestCase(unittest.IsolatedAsyncioTestCase): ) self.assertEqual("silence1", silence_id1) - fake_update_silence.assert_called_once_with("fingerprint1", "user", None) + fake_update_silence.assert_called_once_with( + "fingerprint1", "user", None, force=False + ) fake_create_silence.assert_called_once_with("fingerprint1", "user", None) @patch.object(matrix_alertbot.alertmanager.AlertmanagerClient, "update_silence") @@ -641,7 +688,9 @@ class AlertmanagerClientTestCase(unittest.IsolatedAsyncioTestCase): ) self.assertEqual("silence1", silence_id1) - fake_update_silence.assert_called_once_with("fingerprint1", "user", None) + fake_update_silence.assert_called_once_with( + "fingerprint1", "user", None, force=False + ) fake_create_silence.assert_not_called() @freeze_time(datetime.utcfromtimestamp(0)) diff --git a/tests/test_command.py b/tests/test_command.py index 5ee19f3..b7c3477 100644 --- a/tests/test_command.py +++ b/tests/test_command.py @@ -32,7 +32,11 @@ def cache_get_item(key: str) -> str: async def create_silence( - fingerprint: str, user: str, duration_seconds: Optional[int] = None + fingerprint: str, + user: str, + duration_seconds: Optional[int] = None, + *, + force: bool = True, ) -> str: if fingerprint == "fingerprint1": return "silence1" @@ -42,7 +46,11 @@ async def create_silence( async def create_silence_raise_alertmanager_error( - fingerprint: str, user: str, duration_seconds: Optional[int] = None + fingerprint: str, + user: str, + duration_seconds: Optional[int] = None, + *, + force: bool = True, ) -> str: if fingerprint == "fingerprint1": raise AlertmanagerError @@ -50,7 +58,11 @@ async def create_silence_raise_alertmanager_error( async def create_silence_raise_alert_not_found_error( - fingerprint: str, user: str, duration_seconds: Optional[int] = None + fingerprint: str, + user: str, + duration_seconds: Optional[int] = None, + *, + force: bool = True, ) -> str: if fingerprint == "fingerprint1": raise AlertNotFoundError @@ -80,7 +92,9 @@ class CommandTestCase(unittest.IsolatedAsyncioTestCase): self.fake_cache.__contains__.return_value = True self.fake_alertmanager_client = Mock(spec=AlertmanagerClient) - self.fake_alertmanager_client.create_silence.side_effect = create_silence + self.fake_alertmanager_client.create_or_update_silence.side_effect = ( + create_silence + ) # Create a fake room to play with self.fake_room = Mock(spec=nio.MatrixRoom) @@ -208,7 +222,6 @@ class CommandTestCase(unittest.IsolatedAsyncioTestCase): fake_cache_dict = {self.fake_alert_event_id: "fingerprint1"} self.fake_cache.__getitem__.side_effect = fake_cache_dict.__getitem__ - self.fake_alertmanager_client.create_or_update_silence.return_value = "silence1" command = AckAlertCommand( self.fake_matrix_client, @@ -224,7 +237,7 @@ class CommandTestCase(unittest.IsolatedAsyncioTestCase): # Check that we attempted to create silences self.fake_alertmanager_client.create_or_update_silence.assert_called_once_with( - "fingerprint1", self.fake_sender, None + "fingerprint1", self.fake_sender, None, force=True ) fake_send_text_to_room.assert_called_once_with( self.fake_matrix_client, @@ -241,7 +254,6 @@ class CommandTestCase(unittest.IsolatedAsyncioTestCase): """Tests the callback for InviteMemberEvents""" # Tests that the bot attempts to join a room after being invited to it fake_cache_dict = {self.fake_alert_event_id: "fingerprint1"} - self.fake_alertmanager_client.create_or_update_silence.return_value = "silence1" self.fake_cache.__getitem__.side_effect = fake_cache_dict.__getitem__ @@ -260,7 +272,7 @@ class CommandTestCase(unittest.IsolatedAsyncioTestCase): # Check that we attempted to create silences self.fake_alertmanager_client.create_or_update_silence.assert_called_once_with( - "fingerprint1", self.fake_sender, 864000 + "fingerprint1", self.fake_sender, 864000, force=True ) fake_send_text_to_room.assert_called_once_with( self.fake_matrix_client, @@ -301,7 +313,7 @@ class CommandTestCase(unittest.IsolatedAsyncioTestCase): # Check that we attempted to create silences self.fake_alertmanager_client.create_or_update_silence.assert_called_once_with( - "fingerprint1", self.fake_sender, None + "fingerprint1", self.fake_sender, None, force=True ) fake_send_text_to_room.assert_called_once_with( self.fake_matrix_client, @@ -340,7 +352,7 @@ class CommandTestCase(unittest.IsolatedAsyncioTestCase): # Check that we attempted to create silences self.fake_alertmanager_client.create_or_update_silence.assert_called_once_with( - "fingerprint1", self.fake_sender, None + "fingerprint1", self.fake_sender, None, force=True ) fake_send_text_to_room.assert_called_once_with( self.fake_matrix_client,