Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add service label for alerting integrations #5373

Draft
wants to merge 10 commits into
base: dev
Choose a base branch
from
3 changes: 3 additions & 0 deletions engine/apps/alerts/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,3 +25,6 @@ class AlertGroupState(str, Enum):
ACKNOWLEDGED = "acknowledged"
RESOLVED = "resolved"
SILENCED = "silenced"


SERVICE_LABEL = "service_name"
12 changes: 5 additions & 7 deletions engine/apps/alerts/models/alert.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
from apps.alerts.incident_appearance.templaters import TemplateLoader
from apps.alerts.signals import alert_group_escalation_snapshot_built
from apps.alerts.tasks.distribute_alert import send_alert_create_signal
from apps.labels.alert_group_labels import assign_labels, gather_labels_from_alert_receive_channel_and_raw_request_data
from apps.labels.alert_group_labels import gather_alert_labels, save_alert_group_labels
from apps.labels.types import AlertLabels
from common.jinja_templater import apply_jinja_template_to_alert_payload_and_labels
from common.jinja_templater.apply_jinja_template import (
Expand Down Expand Up @@ -106,13 +106,11 @@ def create(
# This import is here to avoid circular imports
from apps.alerts.models import AlertGroup, AlertGroupLogRecord, AlertReceiveChannel, ChannelFilter

parsed_labels = gather_labels_from_alert_receive_channel_and_raw_request_data(
alert_receive_channel, raw_request_data
)
group_data = Alert.render_group_data(alert_receive_channel, raw_request_data, parsed_labels, is_demo)
alert_labels = gather_alert_labels(alert_receive_channel, raw_request_data)
group_data = Alert.render_group_data(alert_receive_channel, raw_request_data, alert_labels, is_demo)

if channel_filter is None:
channel_filter = ChannelFilter.select_filter(alert_receive_channel, raw_request_data, parsed_labels)
channel_filter = ChannelFilter.select_filter(alert_receive_channel, raw_request_data, alert_labels)

# Get or create group
group, group_created = AlertGroup.objects.get_or_create_grouping(
Expand Down Expand Up @@ -141,7 +139,7 @@ def create(
transaction.on_commit(partial(send_alert_create_signal.apply_async, (alert.pk,)))

if group_created:
assign_labels(group, alert_receive_channel, parsed_labels)
save_alert_group_labels(group, alert_receive_channel, alert_labels)
group.log_records.create(type=AlertGroupLogRecord.TYPE_REGISTERED)
group.log_records.create(type=AlertGroupLogRecord.TYPE_ROUTE_ASSIGNED)

Expand Down
60 changes: 60 additions & 0 deletions engine/apps/alerts/models/alert_receive_channel.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
from django.utils.crypto import get_random_string
from emoji import emojize

from apps.alerts.constants import SERVICE_LABEL
from apps.alerts.grafana_alerting_sync_manager.grafana_alerting_sync import GrafanaAlertingSyncManager
from apps.alerts.integration_options_mixin import IntegrationOptionsMixin
from apps.alerts.models.maintainable_object import MaintainableObject
Expand All @@ -24,6 +25,8 @@
from apps.integrations.legacy_prefix import remove_legacy_prefix
from apps.integrations.metadata import heartbeat
from apps.integrations.tasks import create_alert, create_alertmanager_alerts
from apps.labels.client import LabelsRepoAPIException
from apps.labels.tasks import add_service_label_for_integration
from apps.metrics_exporter.helpers import (
metrics_add_integrations_to_cache,
metrics_remove_deleted_integration_from_cache,
Expand All @@ -48,6 +51,10 @@
logger = logging.getLogger(__name__)


class CreatingServiceNameDynamicLabelFailed(Exception):
"""Raised when failed to create a dynamic service name label"""


class MessagingBackendTemplatesItem:
title: str | None
message: str | None
Expand Down Expand Up @@ -761,6 +768,59 @@ def insight_logs_metadata(self):
result["team"] = "General"
return result

def create_service_name_dynamic_label(self, is_called_async: bool = False):
"""
create_service_name_dynamic_label creates a dynamic label for service_name for Grafana Alerting integration.
Warning: It might make a request to the labels repo API.
That's why it's called in api handlers, not in post_save.
Once we will have labels operator & get rid of syncing labels from repo, this method should be moved
to post_save.
"""
from apps.labels.models import LabelKeyCache

if not self.organization.is_grafana_labels_enabled:
return
if self.integration != AlertReceiveChannel.GRAFANA_ALERTING:
return

# validate that service_name label doesn't exist in already
service_name_label = LabelKeyCache.objects.filter(organization=self.organization, name=SERVICE_LABEL).first()

if service_name_label is not None and self.alert_group_labels_custom is not None:
for k, _, _ in self.alert_group_labels_custom:
if k == service_name_label.id:
return

service_name_dynamic_label = self._build_service_name_label_custom(self.organization)
if service_name_dynamic_label is None:
# if this method was called from a celery task, raise exception to retry it
if is_called_async:
raise CreatingServiceNameDynamicLabelFailed
# otherwise start a celery task to retry the label creation async
add_service_label_for_integration.apply_async((self.id,))
return
self.alert_group_labels_custom = [service_name_dynamic_label] + (self.alert_group_labels_custom or [])
self.save(update_fields=["alert_group_labels_custom"])

@staticmethod
def _build_service_name_label_custom(organization: "Organization") -> DynamicLabelsEntryDB | None:
"""
_build_service_name_label_custom returns `service_name` label template in dynamic label format: [key_id, None, template]
If there is no label key service_name in the cache - it tries to fetch it from the labels repo API.
"""
from apps.labels.models import LabelKeyCache

SERVICE_LABEL_TEMPLATE_FOR_ALERTING_INTEGRATION = "{{ payload.common_labels.service_name }}"

try:
service_label_key = LabelKeyCache.get_or_create_by_name(organization, SERVICE_LABEL)
except LabelsRepoAPIException as e:
logger.error(f"Failed to get or create label key {SERVICE_LABEL} for organization {organization}: {e}")
return None
return (
[service_label_key.id, None, SERVICE_LABEL_TEMPLATE_FOR_ALERTING_INTEGRATION] if service_label_key else None
)


@receiver(post_save, sender=AlertReceiveChannel)
def listen_for_alertreceivechannel_model_save(
Expand Down
Loading
Loading