From d0a86a3105d5fa6daecdae27de6e7d0c2e0aeb89 Mon Sep 17 00:00:00 2001 From: Yulya Artyukhina Date: Fri, 10 Nov 2023 11:09:20 +0100 Subject: [PATCH 1/3] Add command for restarting acknowledge reminder tasks for organization (#3314) # What this PR does Add command for restarting acknowledge reminder tasks for organization. It allows to easily restart these tasks for migrated organizations ## Which issue(s) this PR fixes related to https://github.com/grafana/oncall-private/issues/1955 ## Checklist - [ ] Unit, integration, and e2e (if applicable) tests updated - [x] Documentation added (or `pr:no public docs` PR label added if not required) - [x] `CHANGELOG.md` updated (or `pr:no changelog` PR label added if not required) --- .../commands/restart_acknowledge_reminder.py | 73 +++++++++++++++++++ 1 file changed, 73 insertions(+) create mode 100644 engine/engine/management/commands/restart_acknowledge_reminder.py diff --git a/engine/engine/management/commands/restart_acknowledge_reminder.py b/engine/engine/management/commands/restart_acknowledge_reminder.py new file mode 100644 index 00000000..69622053 --- /dev/null +++ b/engine/engine/management/commands/restart_acknowledge_reminder.py @@ -0,0 +1,73 @@ +from celery import uuid as celery_uuid +from django.core.management import BaseCommand + +from apps.alerts.models import AlertGroup +from apps.alerts.tasks import acknowledge_reminder_task +from apps.user_management.models import Organization + + +class Command(BaseCommand): + """ + Restart acknowledge_reminder_task for organization. Used for migrated organizations. + + Usage example: + `python manage.py restart_acknowledge_reminder -ppk "organization ppk"` - restart task for alert groups from + organizations with this public pk + """ + + def add_arguments(self, parser): + group = parser.add_mutually_exclusive_group(required=True) + + group.add_argument( + "-ppk", "--organization_ppk", type=str, help="Organization public pks to restart reminder for." + ) + + def handle(self, *args, **options): + organization_ppk = options["organization_ppk"] + organization = Organization.objects.get(public_primary_key=organization_ppk) + self.stdout.write( + f"Processing restart acknowledge reminder for alert groups from organization " + f"(id: {organization.id}, ppk: {organization.public_primary_key})..." + ) + if organization.acknowledge_remind_timeout == 0: + self.stdout.write("Organization doesn't have acknowledge reminder setting set") + return + + alert_groups = AlertGroup.objects.filter( + acknowledged=True, + resolved=False, + silenced=False, + maintenance_uuid__isnull=True, + root_alert_group=None, + channel__organization=organization, + ) + if not alert_groups: + self.stdout.write("No affected alert groups.") + return + + self.stdout.write(f"Affected alert groups count: {alert_groups.count()}.") + + tasks = [] + alert_groups_to_update = [] + + for alert_group in alert_groups: + task_id = celery_uuid() + countdown = Organization.ACKNOWLEDGE_REMIND_DELAY[organization.acknowledge_remind_timeout] + alert_group.last_unique_unacknowledge_process_id = task_id + alert_groups_to_update.append(alert_group) + tasks.append( + acknowledge_reminder_task.signature( + args=(alert_group.pk, task_id), immutable=True, task_id=task_id, countdown=countdown + ) + ) + + AlertGroup.objects.bulk_update( + alert_groups_to_update, + ["last_unique_unacknowledge_process_id"], + batch_size=5000, + ) + + for task in tasks: + task.apply_async() + + self.stdout.write("Acknowledge reminder has been restarted for affected alert groups") From ad1f63dbe9fdcce36076644b91d9edf02ace8068 Mon Sep 17 00:00:00 2001 From: Michael Derynck Date: Fri, 10 Nov 2023 06:45:12 -0700 Subject: [PATCH 2/3] AmazonSNS integration exception handling (#3315) # What this PR does Handle OrganizationMoved, OrganizationDeleted and PermissionDenied exceptions same as other integration API views instead of converting to BadRequest. ## Which issue(s) this PR fixes ## Checklist - [x] Unit, integration, and e2e (if applicable) tests updated - [x] Documentation added (or `pr:no public docs` PR label added if not required) - [x] `CHANGELOG.md` updated (or `pr:no changelog` PR label added if not required) --- CHANGELOG.md | 6 +++ engine/apps/integrations/views.py | 5 ++- .../apps/user_management/tests/test_region.py | 45 ++++++++++++++----- 3 files changed, 43 insertions(+), 13 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0999423e..75e81488 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,12 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## Unreleased + +### Fixed + +- Fix AmazonSNS integration to handle exceptions the same as other integrations @mderynck ([#3315](https://github.com/grafana/oncall/pull/3315)) + ## v1.3.56 (2023-11-10) ## v1.3.55 (2023-11-07) diff --git a/engine/apps/integrations/views.py b/engine/apps/integrations/views.py index 359b692b..61e51bea 100644 --- a/engine/apps/integrations/views.py +++ b/engine/apps/integrations/views.py @@ -21,6 +21,7 @@ from apps.integrations.mixins import ( is_ratelimit_ignored, ) from apps.integrations.tasks import create_alert, create_alertmanager_alerts +from apps.user_management.exceptions import OrganizationDeletedException, OrganizationMovedException from common.api_helpers.utils import create_engine_url logger = logging.getLogger(__name__) @@ -31,8 +32,10 @@ class AmazonSNS(BrowsableInstructionMixin, AlertChannelDefiningMixin, Integratio def dispatch(self, *args, **kwargs): try: return super().dispatch(*args, **kwargs) + except (OrganizationMovedException, OrganizationDeletedException, PermissionDenied) as oe: + raise oe except Exception as e: - print(e) + logger.error(f"AmazonSNS - Bad Request (400) {str(e)}") return JsonResponse(status=400, data={}) def handle_message(self, message, payload): diff --git a/engine/apps/user_management/tests/test_region.py b/engine/apps/user_management/tests/test_region.py index 414396d1..6331327c 100644 --- a/engine/apps/user_management/tests/test_region.py +++ b/engine/apps/user_management/tests/test_region.py @@ -9,7 +9,7 @@ from rest_framework.test import APIClient from apps.alerts.models import AlertReceiveChannel from apps.auth_token.auth import ApiTokenAuthentication, ScheduleExportAuthentication, UserScheduleExportAuthentication from apps.auth_token.models import ScheduleExportAuthToken, UserScheduleExportAuthToken -from apps.integrations.views import AlertManagerAPIView +from apps.integrations.views import AlertManagerAPIView, AmazonSNS from apps.schedules.models import OnCallScheduleWeb from apps.user_management.exceptions import OrganizationMovedException @@ -30,19 +30,27 @@ def test_organization_region_delete( @pytest.mark.django_db +@pytest.mark.parametrize( + "integration_type,integration_view", + [ + (AlertReceiveChannel.INTEGRATION_ALERTMANAGER, AlertManagerAPIView()), + ("amazon_sns", AmazonSNS()), + ], +) def test_integration_does_not_raise_exception_organization_moved( make_organization, make_alert_receive_channel, + integration_type, + integration_view, ): organization = make_organization() alert_receive_channel = make_alert_receive_channel( organization=organization, - integration=AlertReceiveChannel.INTEGRATION_ALERTMANAGER, + integration=integration_type, ) try: - am = AlertManagerAPIView() - am.dispatch(alert_channel_key=alert_receive_channel.token) + integration_view.dispatch(alert_channel_key=alert_receive_channel.token) assert False except OrganizationMovedException: assert False @@ -51,21 +59,29 @@ def test_integration_does_not_raise_exception_organization_moved( @pytest.mark.django_db +@pytest.mark.parametrize( + "integration_type,integration_view", + [ + (AlertReceiveChannel.INTEGRATION_ALERTMANAGER, AlertManagerAPIView()), + ("amazon_sns", AmazonSNS()), + ], +) def test_integration_raises_exception_organization_moved( make_organization_and_region, make_alert_receive_channel, + integration_type, + integration_view, ): organization, region = make_organization_and_region() organization.save() alert_receive_channel = make_alert_receive_channel( organization=organization, - integration=AlertReceiveChannel.INTEGRATION_ALERTMANAGER, + integration=integration_type, ) try: - am = AlertManagerAPIView() - am.dispatch(alert_channel_key=alert_receive_channel.token) + integration_view.dispatch(alert_channel_key=alert_receive_channel.token) assert False except OrganizationMovedException as e: assert e.organization == organization @@ -73,24 +89,29 @@ def test_integration_raises_exception_organization_moved( @patch("apps.user_management.middlewares.OrganizationMovedMiddleware.make_request") @pytest.mark.django_db +@pytest.mark.parametrize( + "integration_type", + [ + AlertReceiveChannel.INTEGRATION_ALERTMANAGER, + "amazon_sns", + ], +) def test_organization_moved_middleware( - mocked_make_request, - make_organization_and_region, - make_alert_receive_channel, + mocked_make_request, make_organization_and_region, make_alert_receive_channel, integration_type ): organization, region = make_organization_and_region() organization.save() alert_receive_channel = make_alert_receive_channel( organization=organization, - integration=AlertReceiveChannel.INTEGRATION_ALERTMANAGER, + integration=integration_type, ) expected_message = bytes(f"Redirected to {region.oncall_backend_url}", "utf-8") mocked_make_request.return_value = HttpResponse(expected_message, status=status.HTTP_200_OK) client = APIClient() - url = reverse("integrations:alertmanager", kwargs={"alert_channel_key": alert_receive_channel.token}) + url = reverse(f"integrations:{integration_type}", kwargs={"alert_channel_key": alert_receive_channel.token}) data = {"value": "test"} response = client.post(url, data, format="json") From f5d75f3e940ac313b521ebe7717fe47bd6e16816 Mon Sep 17 00:00:00 2001 From: Michael Derynck Date: Fri, 10 Nov 2023 07:26:34 -0700 Subject: [PATCH 3/3] Update CHANGELOG.md --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 75e81488..37d39e75 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,7 +5,7 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). -## Unreleased +## v1.3.57 (2023-11-10) ### Fixed