def delete_alert_rule(alert_rule, user=None): """ Marks an alert rule as deleted and fires off a task to actually delete it. :param alert_rule: """ if alert_rule.status == AlertRuleStatus.SNAPSHOT.value: raise AlreadyDeletedError() with transaction.atomic(): incidents = Incident.objects.filter(alert_rule=alert_rule) bulk_delete_snuba_subscriptions( list(alert_rule.snuba_query.subscriptions.all())) if incidents: alert_rule.update(status=AlertRuleStatus.SNAPSHOT.value) AlertRuleActivity.objects.create( alert_rule=alert_rule, user=user, type=AlertRuleActivityType.DELETED.value, ) else: alert_rule.delete() if alert_rule.id: # Change the incident status asynchronously, which could take awhile with many incidents due to snapshot creations. tasks.auto_resolve_snapshot_incidents.apply_async( kwargs={"alert_rule_id": alert_rule.id})
def test(self): subscription = create_snuba_subscription( self.project, "something", QueryDatasets.EVENTS, "level:error", QueryAggregations.TOTAL, timedelta(minutes=10), timedelta(minutes=1), [], ) other_subscription = create_snuba_subscription( self.create_project(organization=self.organization), "something", QueryDatasets.EVENTS, "level:error", QueryAggregations.TOTAL, timedelta(minutes=10), timedelta(minutes=1), [], ) subscription_ids = [subscription.id, other_subscription.id] bulk_delete_snuba_subscriptions([subscription, other_subscription]) assert not QuerySubscription.objects.filter( id__in=subscription_ids).exists()
def test(self): with self.tasks(): snuba_query = create_snuba_query( QueryDatasets.EVENTS, "level:error", QueryAggregations.TOTAL, timedelta(minutes=10), timedelta(minutes=1), None, ) subscription = create_snuba_subscription(self.project, "something", snuba_query) snuba_query = create_snuba_query( QueryDatasets.EVENTS, "level:error", QueryAggregations.TOTAL, timedelta(minutes=10), timedelta(minutes=1), None, ) other_subscription = create_snuba_subscription( self.create_project(organization=self.organization), "something", snuba_query) subscription_ids = [subscription.id, other_subscription.id] bulk_delete_snuba_subscriptions([subscription, other_subscription]) assert (QuerySubscription.objects.filter( id__in=subscription_ids, status=QuerySubscription.Status.DELETING.value, subscription_id__isnull=False, ).count() == 2)
def delete_alert_rule(alert_rule): """ Marks an alert rule as deleted and fires off a task to actually delete it. :param alert_rule: """ if alert_rule.status == AlertRuleStatus.SNAPSHOT.value: raise AlreadyDeletedError() with transaction.atomic(): incidents = Incident.objects.filter(alert_rule=alert_rule) bulk_delete_snuba_subscriptions( list(alert_rule.query_subscriptions.all())) if incidents: alert_rule.update(status=AlertRuleStatus.SNAPSHOT.value) for incident in incidents: incident.update(status=IncidentStatus.CLOSED.value) else: alert_rule.delete()
def delete_alert_rule(alert_rule): """ Marks an alert rule as deleted and fires off a task to actually delete it. :param alert_rule: """ if alert_rule.status in ( AlertRuleStatus.PENDING_DELETION.value, AlertRuleStatus.DELETION_IN_PROGRESS.value, ): raise AlreadyDeletedError() with transaction.atomic(): alert_rule.update( # Randomize the name here so that we don't get unique constraint issues # while waiting for the deletion to process name=uuid4().hex, status=AlertRuleStatus.PENDING_DELETION.value, ) bulk_delete_snuba_subscriptions(list(alert_rule.query_subscriptions.all())) tasks.delete_alert_rule.apply_async(kwargs={"alert_rule_id": alert_rule.id})
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
def update_alert_rule( alert_rule, projects=None, name=None, query=None, aggregate=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 aggregate: A string representing the aggregate used in this alert rule :param time_window: Time period to aggregate over, in minutes. :param environment: An optional environment 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 = {} updated_query_fields = {} if name: updated_fields["name"] = name if query is not None: validate_alert_rule_query(query) updated_query_fields["query"] = query if aggregate is not None: updated_query_fields["aggregate"] = aggregate if time_window: updated_query_fields["time_window"] = timedelta(minutes=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) if updated_query_fields or environment != alert_rule.snuba_query.environment: snuba_query = alert_rule.snuba_query updated_query_fields.setdefault("query", snuba_query.query) # XXX: We use the alert rule aggregation here since currently we're # expecting the enum value to be passed. updated_query_fields.setdefault("aggregate", snuba_query.aggregate) updated_query_fields.setdefault( "time_window", timedelta(seconds=snuba_query.time_window) ) update_snuba_query( alert_rule.snuba_query, resolution=timedelta(minutes=DEFAULT_ALERT_RULE_RESOLUTION), environment=environment, **updated_query_fields ) existing_subs = [] if ( query is not None or aggregate 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.snuba_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) return alert_rule
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