Ejemplo n.º 1
0
def migrate_subscriptions(apps, schema_editor):
    QuerySubscription = apps.get_model("sentry", "QuerySubscription")

    for subscription in RangeQuerySetWrapperWithProgressBar(
            QuerySubscription.objects.select_related("snuba_query").all()):
        if subscription.subscription_id is not None:
            subscription_id = None
            try:
                subscription_id = _create_in_snuba(subscription)
            except Exception as e:
                logging.exception(
                    f"failed to recreate {subscription.subscription_id}: {e}")
                continue

            try:
                _delete_from_snuba(
                    QueryDatasets(subscription.snuba_query.dataset),
                    subscription.subscription_id,
                )
            except Exception as e:
                try:
                    # Delete the subscription we just created to avoid orphans
                    _delete_from_snuba(
                        QueryDatasets(subscription.snuba_query.dataset),
                        subscription_id,
                    )
                except Exception as oe:
                    logging.exception(
                        f"failed to delete orphan {subscription_id}: {oe}")

                logging.exception(
                    f"failed to delete {subscription.subscription_id}: {e}")
                continue

            subscription.update(subscription_id=subscription_id)
Ejemplo n.º 2
0
def migrate_subscriptions(apps, schema_editor):
    QuerySubscription = apps.get_model("sentry", "QuerySubscription")
    AppSnubaQueryEventType = apps.get_model("sentry", "SnubaQueryEventType")

    for subscription in RangeQuerySetWrapperWithProgressBar(
            QuerySubscription.objects.select_related("snuba_query").all()):
        if subscription.subscription_id is not None:
            # The migration apps don't build this property, so manually set it.
            raw_event_types = AppSnubaQueryEventType.objects.filter(
                snuba_query=subscription.snuba_query).all()
            event_types = [
                SnubaQueryEventType.EventType(ev.type)
                for ev in raw_event_types
            ]
            setattr(subscription.snuba_query, "event_types", event_types)

            subscription_id = None
            try:
                subscription_id = _create_in_snuba(subscription)
            except Exception as e:
                logging.exception(
                    f"failed to recreate {subscription.subscription_id}: {e}")
                continue

            try:
                _delete_from_snuba(
                    QueryDatasets(subscription.snuba_query.dataset),
                    subscription.subscription_id,
                )
            except Exception as e:
                try:
                    # Delete the subscription we just created to avoid orphans
                    _delete_from_snuba(
                        QueryDatasets(subscription.snuba_query.dataset),
                        subscription_id,
                    )
                except Exception as oe:
                    logging.exception(
                        f"failed to delete orphan {subscription_id}: {oe}")

                logging.exception(
                    f"failed to delete {subscription.subscription_id}: {e}")
                continue

            QuerySubscription.objects.filter(id=subscription.id).update(
                subscription_id=subscription_id)
Ejemplo n.º 3
0
    def handle_message(self, message):
        """
        Parses the value from Kafka, and if valid passes the payload to the callback defined by the
        subscription. If the subscription has been removed, or no longer has a valid callback then
        just log metrics/errors and continue.
        :param message:
        :return:
        """
        with sentry_sdk.push_scope() as scope:
            try:
                with metrics.timer(
                        "snuba_query_subscriber.parse_message_value"):
                    contents = self.parse_message_value(message.value())
            except InvalidMessageError:
                # If the message is in an invalid format, just log the error
                # and continue
                logger.exception(
                    "Subscription update could not be parsed",
                    extra={
                        "offset": message.offset(),
                        "partition": message.partition(),
                        "value": message.value(),
                    },
                )
                return
            scope.set_tag("query_subscription_id", contents["subscription_id"])

            try:
                with metrics.timer(
                        "snuba_query_subscriber.fetch_subscription"):
                    subscription = QuerySubscription.objects.get_from_cache(
                        subscription_id=contents["subscription_id"])
                    if subscription.status != QuerySubscription.Status.ACTIVE.value:
                        metrics.incr(
                            "snuba_query_subscriber.subscription_inactive")
                        return
            except QuerySubscription.DoesNotExist:
                metrics.incr(
                    "snuba_query_subscriber.subscription_doesnt_exist")
                logger.error(
                    "Received subscription update, but subscription does not exist",
                    extra={
                        "offset": message.offset(),
                        "partition": message.partition(),
                        "value": message.value(),
                    },
                )
                try:
                    _delete_from_snuba(self.topic_to_dataset[message.topic()],
                                       contents["subscription_id"])
                except Exception:
                    logger.exception(
                        "Failed to delete unused subscription from snuba.")
                return

            if subscription.type not in subscriber_registry:
                metrics.incr(
                    "snuba_query_subscriber.subscription_type_not_registered")
                logger.error(
                    "Received subscription update, but no subscription handler registered",
                    extra={
                        "offset": message.offset(),
                        "partition": message.partition(),
                        "value": message.value(),
                    },
                )
                return

            logger.debug(
                "query-subscription-consumer.handle_message",
                extra={
                    "timestamp": contents["timestamp"],
                    "query_subscription_id": contents["subscription_id"],
                    "project_id": subscription.project_id,
                    "subscription_dataset": subscription.snuba_query.dataset,
                    "subscription_query": subscription.snuba_query.query,
                    "subscription_aggregation":
                    subscription.snuba_query.aggregate,
                    "subscription_time_window":
                    subscription.snuba_query.time_window,
                    "subscription_resolution":
                    subscription.snuba_query.resolution,
                    "offset": message.offset(),
                    "partition": message.partition(),
                    "value": message.value(),
                },
            )

            callback = subscriber_registry[subscription.type]
            with sentry_sdk.start_span(
                    op="process_message") as span, metrics.timer(
                        "snuba_query_subscriber.callback.duration",
                        instance=subscription.type):
                span.set_data("payload", contents)
                callback(contents, subscription)
Ejemplo n.º 4
0
    def handle_message(self, message: Message) -> None:
        """
        Parses the value from Kafka, and if valid passes the payload to the callback defined by the
        subscription. If the subscription has been removed, or no longer has a valid callback then
        just log metrics/errors and continue.
        :param message:
        :return:
        """
        # set a commit time deadline only after the first message for this batch is seen
        if not self.__batch_deadline:
            self.__batch_deadline = self.commit_batch_timeout_ms / 1000.0 + time.time()

        with sentry_sdk.push_scope() as scope:
            try:
                with metrics.timer("snuba_query_subscriber.parse_message_value"):
                    contents = self.parse_message_value(message.value())
            except InvalidMessageError:
                # If the message is in an invalid format, just log the error
                # and continue
                logger.exception(
                    "Subscription update could not be parsed",
                    extra={
                        "offset": message.offset(),
                        "partition": message.partition(),
                        "value": message.value(),
                    },
                )
                return
            scope.set_tag("query_subscription_id", contents["subscription_id"])

            try:
                with metrics.timer("snuba_query_subscriber.fetch_subscription"):
                    subscription: QuerySubscription = QuerySubscription.objects.get_from_cache(
                        subscription_id=contents["subscription_id"]
                    )
                    if subscription.status != QuerySubscription.Status.ACTIVE.value:
                        metrics.incr("snuba_query_subscriber.subscription_inactive")
                        return
            except QuerySubscription.DoesNotExist:
                metrics.incr("snuba_query_subscriber.subscription_doesnt_exist")
                logger.error(
                    "Received subscription update, but subscription does not exist",
                    extra={
                        "offset": message.offset(),
                        "partition": message.partition(),
                        "value": message.value(),
                    },
                )
                try:
                    if "entity" in contents:
                        entity_key = contents["entity"]
                    else:
                        # XXX(ahmed): Remove this logic. This was kept here as backwards compat
                        # for subscription updates with schema version `2`. However schema version 3
                        # sends the "entity" in the payload
                        entity_regex = r"^(MATCH|match)[ ]*\(([^)]+)\)"
                        entity_match = re.match(entity_regex, contents["request"]["query"])
                        if not entity_match:
                            raise InvalidMessageError(
                                "Unable to fetch entity from query in message"
                            )
                        entity_key = entity_match.group(2)
                    _delete_from_snuba(
                        self.topic_to_dataset[message.topic()],
                        contents["subscription_id"],
                        EntityKey(entity_key),
                    )
                except InvalidMessageError as e:
                    logger.exception(e)
                except Exception:
                    logger.exception("Failed to delete unused subscription from snuba.")
                return

            if subscription.type not in subscriber_registry:
                metrics.incr("snuba_query_subscriber.subscription_type_not_registered")
                logger.error(
                    "Received subscription update, but no subscription handler registered",
                    extra={
                        "offset": message.offset(),
                        "partition": message.partition(),
                        "value": message.value(),
                    },
                )
                return

            sentry_sdk.set_tag("project_id", subscription.project_id)
            sentry_sdk.set_tag("query_subscription_id", contents["subscription_id"])

            callback = subscriber_registry[subscription.type]
            with sentry_sdk.start_span(op="process_message") as span, metrics.timer(
                "snuba_query_subscriber.callback.duration", instance=subscription.type
            ):
                span.set_data("payload", contents)
                span.set_data("subscription_dataset", subscription.snuba_query.dataset)
                span.set_data("subscription_query", subscription.snuba_query.query)
                span.set_data("subscription_aggregation", subscription.snuba_query.aggregate)
                span.set_data("subscription_time_window", subscription.snuba_query.time_window)
                span.set_data("subscription_resolution", subscription.snuba_query.resolution)
                span.set_data("message_offset", message.offset())
                span.set_data("message_partition", message.partition())
                span.set_data("message_value", message.value())

                callback(contents, subscription)