# What this PR does Sometimes plugin sync fails with the following exception: ``` Cannot delete or update a parent row: a foreign key constraint fails (`schedules_oncallschedule`, CONSTRAINT `alerts_oncallschedul_team_id_4e633f4b_fk_user_mana` FOREIGN KEY (`team_id`) REFERENCES `user_management_team` (`id`))' ``` How to reproduce: 1. Create a new Grafana team 2. Create two schedules with different types (e.g. ICal and Web) and assign both schedules to the new team 3. Delete the team in Grafana 4. Trigger plugin sync, the sync will fail with the exception above This happens because the `OnCallSchedule` Django model is a polymorphic model and there's a [known bug](https://github.com/django-polymorphic/django-polymorphic/issues/229) in `django-polymorphic` with deleting related objects when using `SET_NULL` and `CASCADE`. This PR adds non-polymorphic versions of `SET_NULL` and `CASCADE` to use in schedule FKs as per this [comment](https://github.com/django-polymorphic/django-polymorphic/issues/229#issuecomment-398434412). This also applies to two other schedule FKs: `organization` and `user_group`, which are not working properly as well. ## 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)
203 lines
7.5 KiB
Python
203 lines
7.5 KiB
Python
import pytest
|
|
from django.core.exceptions import ObjectDoesNotExist
|
|
from django.urls import reverse
|
|
from django.utils import timezone
|
|
from rest_framework.test import APIClient
|
|
|
|
from apps.alerts.models import AlertGroupLogRecord, AlertReceiveChannel, EscalationPolicy
|
|
from apps.base.models import UserNotificationPolicy, UserNotificationPolicyLogRecord
|
|
from apps.schedules.models import OnCallScheduleICal, OnCallScheduleWeb
|
|
from apps.telegram.models import TelegramMessage
|
|
from apps.twilioapp.constants import TwilioCallStatuses, TwilioMessageStatuses
|
|
from apps.user_management.models import Organization
|
|
|
|
|
|
@pytest.mark.django_db
|
|
def test_organization_soft_delete(
|
|
make_organization_and_user_with_token,
|
|
make_alert_receive_channel,
|
|
):
|
|
organization, _, token = make_organization_and_user_with_token()
|
|
alert_receive_channel = make_alert_receive_channel(
|
|
organization=organization, integration=AlertReceiveChannel.INTEGRATION_ALERTMANAGER
|
|
)
|
|
|
|
org_id = organization.id
|
|
organization.delete()
|
|
|
|
deleted_organization = Organization.objects_with_deleted.get(id=org_id)
|
|
# check if org soft-deleted
|
|
assert deleted_organization.deleted_at is not None
|
|
|
|
# check if public api responds with 404
|
|
client = APIClient()
|
|
url = reverse("api-public:integrations-list")
|
|
response = client.get(url, format="json", HTTP_AUTHORIZATION=f"{token}")
|
|
|
|
assert response.status_code == 404
|
|
|
|
# check if alert receiver view responds with 403
|
|
url = reverse("integrations:alertmanager", kwargs={"alert_channel_key": alert_receive_channel.token})
|
|
data = {"a": "b"}
|
|
response = client.post(url, data, format="json")
|
|
assert response.status_code == 403
|
|
|
|
|
|
@pytest.mark.django_db
|
|
def test_organization_hard_delete(
|
|
make_organization,
|
|
make_user,
|
|
make_team,
|
|
make_slack_team_identity,
|
|
make_slack_user_identity,
|
|
make_slack_message,
|
|
make_slack_action_record,
|
|
make_schedule,
|
|
make_custom_action,
|
|
make_alert_receive_channel,
|
|
make_escalation_chain,
|
|
make_escalation_policy,
|
|
make_channel_filter,
|
|
make_user_notification_policy,
|
|
make_telegram_user_connector,
|
|
make_telegram_channel,
|
|
make_telegram_verification_code,
|
|
make_telegram_channel_verification_code,
|
|
make_telegram_message,
|
|
make_alert,
|
|
make_alert_group,
|
|
make_alert_group_log_record,
|
|
make_user_notification_policy_log_record,
|
|
make_sms,
|
|
make_phone_call,
|
|
make_token_for_organization,
|
|
make_public_api_token,
|
|
make_invitation,
|
|
make_resolution_note,
|
|
make_resolution_note_slack_message,
|
|
):
|
|
slack_team_identity = make_slack_team_identity()
|
|
organization = make_organization(slack_team_identity=slack_team_identity)
|
|
|
|
slack_user_identity_1 = make_slack_user_identity(slack_team_identity=slack_team_identity, slack_id="USER_1")
|
|
slack_user_identity_2 = make_slack_user_identity(slack_team_identity=slack_team_identity, slack_id="USER_2")
|
|
|
|
user_1 = make_user(organization=organization, slack_user_identity=slack_user_identity_1)
|
|
user_2 = make_user(organization=organization, slack_user_identity=slack_user_identity_2)
|
|
|
|
user_notification_policy = make_user_notification_policy(
|
|
user=user_1, step=UserNotificationPolicy.Step.WAIT, wait_delay=timezone.timedelta(minutes=15), important=False
|
|
)
|
|
|
|
team = make_team(organization=organization)
|
|
team.users.add(user_1)
|
|
|
|
# Creating different types of schedules to check that deletion works well with PolymorphicModel
|
|
schedule_web = make_schedule(organization=organization, schedule_class=OnCallScheduleWeb)
|
|
schedule_ical = make_schedule(organization=organization, schedule_class=OnCallScheduleICal)
|
|
|
|
custom_action = make_custom_action(organization=organization)
|
|
|
|
escalation_chain = make_escalation_chain(organization=organization)
|
|
escalation_policy = make_escalation_policy(
|
|
escalation_chain=escalation_chain,
|
|
escalation_policy_step=EscalationPolicy.STEP_WAIT,
|
|
wait_delay=EscalationPolicy.ONE_MINUTE,
|
|
last_notified_user=user_1,
|
|
)
|
|
escalation_policy.notify_to_users_queue.set([user_1, user_2])
|
|
|
|
alert_receive_channel = make_alert_receive_channel(organization=organization, author=user_1)
|
|
channel_filter = make_channel_filter(alert_receive_channel, is_default=True, escalation_chain=escalation_chain)
|
|
|
|
alert_group = make_alert_group(
|
|
alert_receive_channel=alert_receive_channel,
|
|
acknowledged_by_user=user_1,
|
|
silenced_by_user=user_2,
|
|
wiped_by=user_2,
|
|
)
|
|
|
|
alert = make_alert(alert_group=alert_group, raw_request_data={})
|
|
alert_group.resolved_by_alert = alert
|
|
alert_group.save(update_fields=["resolved_by_alert"])
|
|
|
|
user_notification_policy_log_record = make_user_notification_policy_log_record(
|
|
author=user_1,
|
|
type=UserNotificationPolicyLogRecord.TYPE_PERSONAL_NOTIFICATION_FAILED,
|
|
notification_policy=user_notification_policy,
|
|
notification_step=user_notification_policy.step,
|
|
notification_channel=user_notification_policy.notify_by,
|
|
alert_group=alert_group,
|
|
)
|
|
|
|
sms = make_sms(
|
|
receiver=user_1, status=TwilioMessageStatuses.SENT, represents_alert=alert, represents_alert_group=alert_group
|
|
)
|
|
|
|
phone_call = make_phone_call(
|
|
receiver=user_1, status=TwilioCallStatuses.COMPLETED, represents_alert=alert, represents_alert_group=alert_group
|
|
)
|
|
|
|
telegram_user_connector = make_telegram_user_connector(user=user_1)
|
|
telegram_channel = make_telegram_channel(organization=organization)
|
|
telegram_verification_code = make_telegram_verification_code(user=user_1)
|
|
telegram_channel_verification_code = make_telegram_channel_verification_code(
|
|
organization=organization, author=user_1
|
|
)
|
|
telegram_message = make_telegram_message(alert_group=alert_group, message_type=TelegramMessage.ALERT_GROUP_MESSAGE)
|
|
|
|
slack_message = make_slack_message(alert_group=alert_group)
|
|
slack_action_record = make_slack_action_record(organization=organization, user=user_1)
|
|
|
|
plugin_token, _ = make_token_for_organization(organization)
|
|
public_api_token, _ = make_public_api_token(user_1, organization)
|
|
|
|
invitation = make_invitation(alert_group=alert_group, author=user_1, invitee=user_2)
|
|
|
|
alert_group_log_record = make_alert_group_log_record(
|
|
alert_group=alert_group, author=user_1, type=AlertGroupLogRecord.TYPE_ACK, invitation=invitation
|
|
)
|
|
|
|
resolution_note_slack_message = make_resolution_note_slack_message(
|
|
alert_group=alert_group, user=user_1, added_by_user=user_2
|
|
)
|
|
resolution_note = make_resolution_note(
|
|
alert_group=alert_group, author=user_1, resolution_note_slack_message=resolution_note_slack_message
|
|
)
|
|
|
|
cascading_objects = [
|
|
user_1,
|
|
user_2,
|
|
team,
|
|
user_notification_policy,
|
|
schedule_web,
|
|
schedule_ical,
|
|
custom_action,
|
|
escalation_chain,
|
|
escalation_policy,
|
|
alert_receive_channel,
|
|
channel_filter,
|
|
alert_group,
|
|
alert,
|
|
alert_group_log_record,
|
|
user_notification_policy_log_record,
|
|
phone_call,
|
|
sms,
|
|
telegram_message,
|
|
telegram_user_connector,
|
|
telegram_channel,
|
|
telegram_verification_code,
|
|
telegram_channel_verification_code,
|
|
slack_message,
|
|
slack_action_record,
|
|
plugin_token,
|
|
public_api_token,
|
|
invitation,
|
|
resolution_note,
|
|
resolution_note_slack_message,
|
|
]
|
|
|
|
organization.hard_delete()
|
|
for obj in cascading_objects:
|
|
with pytest.raises(ObjectDoesNotExist):
|
|
obj.refresh_from_db()
|