From 7dd726622a2d5dad15035e3998293969aaf33010 Mon Sep 17 00:00:00 2001 From: Ildar Iskhakov Date: Tue, 18 Apr 2023 11:31:49 +0800 Subject: [PATCH] =?UTF-8?q?Add=20endpoints=20to=20start=20and=20stop=20mai?= =?UTF-8?q?ntenance=20in=20alert=20receive=20channel=20=E2=80=A6=20(#1755)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit …private api # What this PR does ## Which issue(s) this PR fixes ## Checklist - [ ] Unit, integration, and e2e (if applicable) tests updated - [ ] Documentation added (or `pr:no public docs` PR label added if not required) - [ ] `CHANGELOG.md` updated (or `pr:no changelog` PR label added if not required) --- CHANGELOG.md | 1 + .../api/tests/test_alert_receive_channel.py | 65 +++++++++++++++++++ .../apps/api/views/alert_receive_channel.py | 42 +++++++++++- engine/apps/api/views/maintenance.py | 6 ++ 4 files changed, 113 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 198daae8..5cdf60a4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Added preview and migration API endpoints for route migration from regex into jinja2 ([1715](https://github.com/grafana/oncall/pull/1715)) - Helm chart: add the option to use a helm hook for the migration job ([1386](https://github.com/grafana/oncall/pull/1386)) +- Add endpoints to start and stop maintenance in alert receive channel private api ([1755](https://github.com/grafana/oncall/pull/1755)) - Send demo alert with dynamic payload and get demo payload example on private api ([1700](https://github.com/grafana/oncall/pull/1700)) ## v1.2.11 (2023-04-14) diff --git a/engine/apps/api/tests/test_alert_receive_channel.py b/engine/apps/api/tests/test_alert_receive_channel.py index a1b47b60..53996c56 100644 --- a/engine/apps/api/tests/test_alert_receive_channel.py +++ b/engine/apps/api/tests/test_alert_receive_channel.py @@ -669,3 +669,68 @@ def test_get_alert_receive_channels_direct_paging_present_for_filters( # Check direct paging integration is in the response assert response.status_code == status.HTTP_200_OK assert response.json()[0]["value"] == alert_receive_channel.public_primary_key + + +@pytest.mark.django_db +def test_start_maintenance_integration( + make_user_auth_headers, + make_organization_and_user_with_plugin_token, + make_escalation_chain, + make_alert_receive_channel, +): + + organization, user, token = make_organization_and_user_with_plugin_token() + make_escalation_chain(organization) + alert_receive_channel = make_alert_receive_channel(organization) + + client = APIClient() + + url = reverse( + "api-internal:alert_receive_channel-start-maintenance", kwargs={"pk": alert_receive_channel.public_primary_key} + ) + + data = { + "mode": AlertReceiveChannel.MAINTENANCE, + "duration": AlertReceiveChannel.DURATION_ONE_HOUR.total_seconds(), + "type": "alert_receive_channel", + } + response = client.post(url, data=data, format="json", **make_user_auth_headers(user, token)) + + alert_receive_channel.refresh_from_db() + assert response.status_code == status.HTTP_200_OK + assert alert_receive_channel.maintenance_mode == AlertReceiveChannel.MAINTENANCE + assert alert_receive_channel.maintenance_duration == AlertReceiveChannel.DURATION_ONE_HOUR + assert alert_receive_channel.maintenance_uuid is not None + assert alert_receive_channel.maintenance_started_at is not None + assert alert_receive_channel.maintenance_author is not None + + +@pytest.mark.django_db +def test_stop_maintenance_integration( + mock_start_disable_maintenance_task, + make_user_auth_headers, + make_organization_and_user_with_plugin_token, + make_escalation_chain, + make_alert_receive_channel, +): + organization, user, token = make_organization_and_user_with_plugin_token() + make_escalation_chain(organization) + alert_receive_channel = make_alert_receive_channel(organization) + client = APIClient() + mode = AlertReceiveChannel.MAINTENANCE + duration = AlertReceiveChannel.DURATION_ONE_HOUR.seconds + alert_receive_channel.start_maintenance(mode, duration, user) + url = reverse( + "api-internal:alert_receive_channel-stop-maintenance", kwargs={"pk": alert_receive_channel.public_primary_key} + ) + data = { + "type": "alert_receive_channel", + } + response = client.post(url, data=data, format="json", **make_user_auth_headers(user, token)) + alert_receive_channel.refresh_from_db() + assert response.status_code == status.HTTP_200_OK + assert alert_receive_channel.maintenance_mode is None + assert alert_receive_channel.maintenance_duration is None + assert alert_receive_channel.maintenance_uuid is None + assert alert_receive_channel.maintenance_started_at is None + assert alert_receive_channel.maintenance_author is None diff --git a/engine/apps/api/views/alert_receive_channel.py b/engine/apps/api/views/alert_receive_channel.py index ff401793..5331dae3 100644 --- a/engine/apps/api/views/alert_receive_channel.py +++ b/engine/apps/api/views/alert_receive_channel.py @@ -9,6 +9,7 @@ from rest_framework.response import Response from rest_framework.viewsets import ModelViewSet from apps.alerts.models import AlertReceiveChannel +from apps.alerts.models.maintainable_object import MaintainableObject from apps.api.permissions import RBACPermission from apps.api.serializers.alert_receive_channel import ( AlertReceiveChannelSerializer, @@ -26,7 +27,7 @@ from common.api_helpers.mixins import ( TeamFilteringMixin, UpdateSerializerMixin, ) -from common.exceptions import TeamCanNotBeChangedError, UnableToSendDemoAlert +from common.exceptions import MaintenanceCouldNotBeStartedError, TeamCanNotBeChangedError, UnableToSendDemoAlert from common.insight_log import EntityEvent, write_resource_insight_log @@ -95,6 +96,8 @@ class AlertReceiveChannelView( "destroy": [RBACPermission.Permissions.INTEGRATIONS_WRITE], "change_team": [RBACPermission.Permissions.INTEGRATIONS_WRITE], "filters": [RBACPermission.Permissions.INTEGRATIONS_READ], + "start_maintenance": [RBACPermission.Permissions.INTEGRATIONS_WRITE], + "stop_maintenance": [RBACPermission.Permissions.INTEGRATIONS_WRITE], } def create(self, request, *args, **kwargs): @@ -249,3 +252,40 @@ class AlertReceiveChannelView( filter_options = list(filter(lambda f: filter_name in f["name"], filter_options)) return Response(filter_options) + + @action(detail=True, methods=["post"]) + def start_maintenance(self, request, pk): + instance = self.get_queryset(eager=False).get(public_primary_key=pk) + + mode = request.data.get("mode", None) + duration = request.data.get("duration", None) + try: + mode = int(mode) + except (ValueError, TypeError): + raise BadRequest(detail={"mode": ["Invalid mode"]}) + if mode not in [MaintainableObject.DEBUG_MAINTENANCE, MaintainableObject.MAINTENANCE]: + raise BadRequest(detail={"mode": ["Unknown mode"]}) + try: + duration = int(duration) + except (ValueError, TypeError): + raise BadRequest(detail={"duration": ["Invalid duration"]}) + if duration not in MaintainableObject.maintenance_duration_options_in_seconds(): + raise BadRequest(detail={"mode": ["Unknown duration"]}) + + try: + instance.start_maintenance(mode, duration, request.user) + except MaintenanceCouldNotBeStartedError as e: + if type(instance) == AlertReceiveChannel: + detail = {"alert_receive_channel_id": ["Already on maintenance"]} + else: + detail = str(e) + raise BadRequest(detail=detail) + + return Response(status=status.HTTP_200_OK) + + @action(detail=True, methods=["post"]) + def stop_maintenance(self, request, pk): + instance = self.get_queryset(eager=False).get(public_primary_key=pk) + user = request.user + instance.force_disable_maintenance(user) + return Response(status=status.HTTP_200_OK) diff --git a/engine/apps/api/views/maintenance.py b/engine/apps/api/views/maintenance.py index 29042acb..c875d65c 100644 --- a/engine/apps/api/views/maintenance.py +++ b/engine/apps/api/views/maintenance.py @@ -39,6 +39,8 @@ class GetObjectMixin: class MaintenanceAPIView(APIView): + """Deprecated. Maintenance management is now performed on integrations page (alert_receive_channel/ endpoint))""" + authentication_classes = (PluginAuthentication,) permission_classes = (IsAuthenticated, RBACPermission) @@ -101,6 +103,8 @@ class MaintenanceAPIView(APIView): class MaintenanceStartAPIView(GetObjectMixin, APIView): + """Deprecated. Maintenance management is now performed on integrations page (alert_receive_channel/ endpoint))""" + authentication_classes = (PluginAuthentication,) permission_classes = (IsAuthenticated, RBACPermission) rbac_permissions = { @@ -137,6 +141,8 @@ class MaintenanceStartAPIView(GetObjectMixin, APIView): class MaintenanceStopAPIView(GetObjectMixin, APIView): + """Deprecated. Maintenance management is now performed on integrations page (alert_receive_channel/ endpoint))""" + authentication_classes = (PluginAuthentication,) permission_classes = (IsAuthenticated, RBACPermission) rbac_permissions = {