Beispiel #1
0
def update_alert_rule(
    alert_rule,
    projects=None,
    name=None,
    query=None,
    aggregation=None,
    time_window=None,
    environment=None,
    threshold_period=None,
    include_all_projects=None,
    excluded_projects=None,
):
    """
    Updates an alert rule.

    :param alert_rule: The alert rule to update
    :param excluded_projects: List of projects to subscribe to the rule. Ignored if
    `include_all_projects` is True
    :param name: Name for the alert rule. This will be used as part of the
    incident name, and must be unique per project.
    :param query: An event search query to subscribe to and monitor for alerts
    :param aggregation: An AlertRuleAggregation that we want to fetch for this alert rule
    :param time_window: Time period to aggregate over, in minutes.
    :param environment: List of environments that this rule applies to
    :param threshold_period: How many update periods the value of the
    subscription needs to exceed the threshold before triggering
    :param include_all_projects: Whether to include all current and future projects
    from this organization
    :param excluded_projects: List of projects to exclude if we're using
    `include_all_projects`. Ignored otherwise.
    :return: The updated `AlertRule`
    """
    if (name and alert_rule.name != name
            and AlertRule.objects.filter(organization=alert_rule.organization,
                                         name=name).exists()):
        raise AlertRuleNameAlreadyUsedError()

    updated_fields = {}
    if name:
        updated_fields["name"] = name
    if query is not None:
        validate_alert_rule_query(query)
        updated_fields["query"] = query
    if aggregation is not None:
        updated_fields["aggregation"] = aggregation.value
    if time_window:
        updated_fields["time_window"] = time_window
    if threshold_period:
        updated_fields["threshold_period"] = threshold_period
    if include_all_projects is not None:
        updated_fields["include_all_projects"] = include_all_projects

    with transaction.atomic():
        incidents = Incident.objects.filter(alert_rule=alert_rule).exists()
        if incidents:
            snapshot_alert_rule(alert_rule)
        alert_rule.update(**updated_fields)

        existing_subs = []
        if (query is not None or aggregation is not None
                or time_window is not None or projects is not None
                or include_all_projects is not None
                or excluded_projects is not None):
            existing_subs = alert_rule.query_subscriptions.all(
            ).select_related("project")

        new_projects = []
        deleted_subs = []

        if not alert_rule.include_all_projects:
            # We don't want to have any exclusion rows present if we're not in
            # `include_all_projects` mode
            get_excluded_projects_for_alert_rule(alert_rule).delete()

        if alert_rule.include_all_projects:
            if include_all_projects or excluded_projects is not None:
                # If we're in `include_all_projects` mode, we want to just fetch
                # projects that aren't already subscribed, and haven't been excluded so
                # we can add them.
                excluded_project_ids = ({p.id
                                         for p in excluded_projects}
                                        if excluded_projects else set())
                project_exclusions = get_excluded_projects_for_alert_rule(
                    alert_rule)
                project_exclusions.exclude(
                    project_id__in=excluded_project_ids).delete()
                existing_excluded_project_ids = {
                    pe.project_id
                    for pe in project_exclusions
                }
                new_exclusions = [
                    AlertRuleExcludedProjects(alert_rule=alert_rule,
                                              project_id=project_id)
                    for project_id in excluded_project_ids
                    if project_id not in existing_excluded_project_ids
                ]
                AlertRuleExcludedProjects.objects.bulk_create(new_exclusions)

                new_projects = Project.objects.filter(
                    organization=alert_rule.organization).exclude(
                        id__in=set([sub.project_id for sub in existing_subs])
                        | excluded_project_ids)
                # If we're subscribed to any of the excluded projects then we want to
                # remove those subscriptions
                deleted_subs = [
                    sub for sub in existing_subs
                    if sub.project_id in excluded_project_ids
                ]
        elif projects is not None:
            existing_project_slugs = {
                sub.project.slug
                for sub in existing_subs
            }
            # Determine whether we've added any new projects as part of this update
            new_projects = [
                project for project in projects
                if project.slug not in existing_project_slugs
            ]
            updated_project_slugs = {project.slug for project in projects}
            # Find any subscriptions that were removed as part of this update
            deleted_subs = [
                sub for sub in existing_subs
                if sub.project.slug not in updated_project_slugs
            ]

        if new_projects:
            subscribe_projects_to_alert_rule(alert_rule, new_projects)

        if deleted_subs:
            bulk_delete_snuba_subscriptions(deleted_subs)
            # Remove any deleted subscriptions from `existing_subscriptions`, so that
            # if we need to update any subscriptions we don't end up doing it twice. We
            # don't add new subscriptions here since they'll already have the updated
            # values
            existing_subs = [sub for sub in existing_subs if sub.id]

        if environment:
            # Delete rows we don't have present in the updated data.
            AlertRuleEnvironment.objects.filter(alert_rule=alert_rule).exclude(
                environment__in=environment).delete()
            for e in environment:
                AlertRuleEnvironment.objects.get_or_create(
                    alert_rule=alert_rule, environment=e)
        else:
            AlertRuleEnvironment.objects.filter(alert_rule=alert_rule).delete()

        if existing_subs and (query is not None or aggregation is not None
                              or time_window is not None):
            # If updating any subscription details, update related Snuba subscriptions
            # too
            bulk_update_snuba_subscriptions(
                existing_subs,
                alert_rule.query,
                QueryAggregations(alert_rule.aggregation),
                timedelta(minutes=alert_rule.time_window),
                timedelta(minutes=DEFAULT_ALERT_RULE_RESOLUTION),
                list(alert_rule.environment.all()),
            )

    return alert_rule
Beispiel #2
0
def update_alert_rule(
    alert_rule,
    projects=None,
    name=None,
    threshold_type=None,
    query=None,
    aggregation=None,
    time_window=None,
    alert_threshold=None,
    resolve_threshold=None,
    threshold_period=None,
):
    """
    Updates an alert rule.

    :param alert_rule: The alert rule to update
    :param name: Name for the alert rule. This will be used as part of the
    incident name, and must be unique per project.
    :param threshold_type: An AlertRuleThresholdType
    :param query: An event search query to subscribe to and monitor for alerts
    :param aggregation: An AlertRuleAggregation that we want to fetch for this alert rule
    :param time_window: Time period to aggregate over, in minutes.
    :param alert_threshold: Value that the subscription needs to reach to
    trigger the alert
    :param resolve_threshold: Value that the subscription needs to reach to
    resolve the alert
    :param threshold_period: How many update periods the value of the
    subscription needs to exceed the threshold before triggering
    :return: The updated `AlertRule`
    """
    if (name and alert_rule.name != name
            and AlertRule.objects.filter(organization=alert_rule.organization,
                                         name=name).exists()):
        raise AlertRuleNameAlreadyUsedError()

    updated_fields = {}
    if name:
        updated_fields["name"] = name
    if threshold_type:
        updated_fields["threshold_type"] = threshold_type.value
    if query is not None:
        validate_alert_rule_query(query)
        updated_fields["query"] = query
    if aggregation is not None:
        updated_fields["aggregation"] = aggregation.value
    if time_window:
        updated_fields["time_window"] = time_window
    if alert_threshold is not None:
        updated_fields["alert_threshold"] = alert_threshold
    if resolve_threshold is not None:
        updated_fields["resolve_threshold"] = resolve_threshold
    if threshold_period:
        updated_fields["threshold_period"] = threshold_period

    with transaction.atomic():
        alert_rule.update(**updated_fields)
        existing_subs = []
        if (query is not None or aggregation is not None
                or time_window is not None or projects is not None):
            existing_subs = alert_rule.query_subscriptions.all(
            ).select_related("project")

        if projects is not None:
            existing_project_slugs = {
                sub.project.slug
                for sub in existing_subs
            }
            # Determine whether we've added any new projects as part of this update
            new_projects = [
                project for project in projects
                if project.slug not in existing_project_slugs
            ]
            updated_project_slugs = {project.slug for project in projects}
            # Find any subscriptions that were removed as part of this update
            deleted_subs = [
                sub for sub in existing_subs
                if sub.project.slug not in updated_project_slugs
            ]
            if new_projects:
                new_subscriptions = bulk_create_snuba_subscriptions(
                    new_projects,
                    tasks.INCIDENTS_SNUBA_SUBSCRIPTION_TYPE,
                    QueryDatasets(alert_rule.dataset),
                    alert_rule.query,
                    QueryAggregations(alert_rule.aggregation),
                    alert_rule.time_window,
                    DEFAULT_ALERT_RULE_RESOLUTION,
                )
                subscription_links = [
                    AlertRuleQuerySubscription(query_subscription=subscription,
                                               alert_rule=alert_rule)
                    for subscription in new_subscriptions
                ]
                AlertRuleQuerySubscription.objects.bulk_create(
                    subscription_links)

            if deleted_subs:
                bulk_delete_snuba_subscriptions(deleted_subs)

            # Remove any deleted subscriptions from `existing_subscriptions`, so that
            # if we need to update any subscriptions we don't end up doing it twice. We
            # don't add new subscriptions here since they'll already have the updated
            # values
            existing_subs = [sub for sub in existing_subs if sub.id]

        if existing_subs and (query is not None or aggregation is not None
                              or time_window is not None):
            # If updating any subscription details, update related Snuba subscriptions
            # too
            bulk_update_snuba_subscriptions(
                existing_subs,
                alert_rule.query,
                QueryAggregations(alert_rule.aggregation),
                alert_rule.time_window,
                DEFAULT_ALERT_RULE_RESOLUTION,
            )

    return alert_rule