Пример #1
0
    def is_rate_limited(self, project, key=None, timestamp=None):
        if timestamp is None:
            timestamp = time()

        quotas = self.get_quotas(project, key=key)

        # If there are no quotas to actually check, skip the trip to the database.
        if not quotas:
            return NotRateLimited()

        keys = []
        args = []
        for quota in quotas:
            if quota.limit == 0:
                # A zero-sized quota is the absolute worst-case. Do not call
                # into Redis at all, and do not increment any keys, as one
                # quota has reached capacity (this is how regular quotas behave
                # as well).
                assert quota.window is None
                assert not quota.should_track
                return RateLimited(retry_after=None,
                                   reason_code=quota.reason_code)

            assert quota.should_track

            shift = project.organization_id % quota.window
            key = self.__get_redis_key(quota, timestamp, shift,
                                       project.organization_id)
            return_key = self.get_refunded_quota_key(key)
            keys.extend((key, return_key))
            expiry = self.get_next_period_start(quota.window, shift,
                                                timestamp) + self.grace

            # limit=None is represented as limit=-1 in lua
            lua_quota = quota.limit if quota.limit is not None else -1
            args.extend((lua_quota, int(expiry)))

        if not keys or not args:
            return NotRateLimited()

        client = self.__get_redis_client(six.text_type(
            project.organization_id))
        rejections = is_rate_limited(client, keys, args)

        if not any(rejections):
            return NotRateLimited()

        worst_case = (0, None)
        for quota, rejected in zip(quotas, rejections):
            if not rejected:
                continue

            shift = project.organization_id % quota.window
            delay = self.get_next_period_start(quota.window, shift,
                                               timestamp) - timestamp
            if delay > worst_case[0]:
                worst_case = (delay, quota.reason_code)

        return RateLimited(retry_after=worst_case[0],
                           reason_code=worst_case[1])
Пример #2
0
    def is_rate_limited(self, project, key=None, timestamp=None):
        if timestamp is None:
            timestamp = time()

        quotas = [
            quota for quota in self.get_quotas(project, key=key)
            # x = (key, limit, interval)
            if
            quota.limit > 0  # a zero limit means "no limit", not "reject all"
        ]

        # If there are no quotas to actually check, skip the trip to the database.
        if not quotas:
            return NotRateLimited()

        def get_next_period_start(interval, shift):
            """Return the timestamp when the next rate limit period begins for an interval."""
            return ((timestamp // interval) + 1) * interval - shift

        keys = []
        args = []
        for quota in quotas:
            shift = project.organization_id % quota.window
            keys.append(
                self.__get_redis_key(quota.key, timestamp, quota.window,
                                     shift))
            expiry = get_next_period_start(quota.window, shift) + self.grace
            args.extend((quota.limit, int(expiry)))

        client = self.cluster.get_local_client_for_key(
            six.text_type(project.organization.pk))
        rejections = is_rate_limited(client, keys, args)
        if any(rejections):
            enforce = False
            worst_case = (0, None)
            for quota, rejected in zip(quotas, rejections):
                if not rejected:
                    continue
                if quota.enforce:
                    enforce = True
                    shift = project.organization_id % quota.window
                    delay = get_next_period_start(quota.window,
                                                  shift) - timestamp
                    if delay > worst_case[0]:
                        worst_case = (delay, quota.reason_code)
            if enforce:
                return RateLimited(
                    retry_after=worst_case[0],
                    reason_code=worst_case[1],
                )
        return NotRateLimited()
Пример #3
0
    def is_rate_limited(self, project, key=None, timestamp=None):
        if timestamp is None:
            timestamp = time()

        quotas = self.get_quotas_with_limits(project, key=key)

        # If there are no quotas to actually check, skip the trip to the database.
        if not quotas:
            return NotRateLimited()

        keys = []
        args = []
        for quota in quotas:
            shift = project.organization_id % quota.window
            key = self.__get_redis_key(quota.key, timestamp, quota.window,
                                       shift)
            return_key = self.get_refunded_quota_key(key)
            keys.extend((key, return_key))
            expiry = self.get_next_period_start(quota.window, shift,
                                                timestamp) + self.grace
            args.extend((quota.limit, int(expiry)))

        client = self.cluster.get_local_client_for_key(
            six.text_type(project.organization_id))
        rejections = is_rate_limited(client, keys, args)
        if any(rejections):
            enforce = False
            worst_case = (0, None)
            for quota, rejected in zip(quotas, rejections):
                if not rejected:
                    continue
                if quota.enforce:
                    enforce = True
                    shift = project.organization_id % quota.window
                    delay = self.get_next_period_start(quota.window, shift,
                                                       timestamp) - timestamp
                    if delay > worst_case[0]:
                        worst_case = (delay, quota.reason_code)
            if enforce:
                return RateLimited(
                    retry_after=worst_case[0],
                    reason_code=worst_case[1],
                )
        return NotRateLimited()
Пример #4
0
    def is_rate_limited(self, project, key=None, timestamp=None):
        # XXX: This is effectively deprecated and scheduled for removal. Event
        # ingestion quotas are now enforced in Relay. This function will be
        # deleted once the Python store endpoints are removed.

        if timestamp is None:
            timestamp = time()

        # Relay supports separate rate limiting per data category and and can
        # handle scopes explicitly. This function implements a simplified logic
        # that treats all events the same and ignores transaction rate limits.
        # Thus, we filter for (1) no categories, which implies this quota
        # affects all data, and (2) quotas that specify `error` events.
        quotas = [
            q for q in self.get_quotas(project, key=key)
            if not q.categories or DataCategory.ERROR in q.categories
        ]

        # If there are no quotas to actually check, skip the trip to the database.
        if not quotas:
            return NotRateLimited()

        keys = []
        args = []
        for quota in quotas:
            if quota.limit == 0:
                # A zero-sized quota is the absolute worst-case. Do not call
                # into Redis at all, and do not increment any keys, as one
                # quota has reached capacity (this is how regular quotas behave
                # as well).
                assert quota.window is None
                assert not quota.should_track
                return RateLimited(retry_after=None,
                                   reason_code=quota.reason_code)

            assert quota.should_track

            shift = project.organization_id % quota.window
            key = self.__get_redis_key(quota, timestamp, shift,
                                       project.organization_id)
            return_key = self.get_refunded_quota_key(key)
            keys.extend((key, return_key))
            expiry = self.get_next_period_start(quota.window, shift,
                                                timestamp) + self.grace

            # limit=None is represented as limit=-1 in lua
            lua_quota = quota.limit if quota.limit is not None else -1
            args.extend((lua_quota, int(expiry)))

        if not keys or not args:
            return NotRateLimited()

        client = self.__get_redis_client(six.text_type(
            project.organization_id))
        rejections = is_rate_limited(client, keys, args)

        if not any(rejections):
            return NotRateLimited()

        worst_case = (0, None)
        for quota, rejected in zip(quotas, rejections):
            if not rejected:
                continue

            shift = project.organization_id % quota.window
            delay = self.get_next_period_start(quota.window, shift,
                                               timestamp) - timestamp
            if delay > worst_case[0]:
                worst_case = (delay, quota.reason_code)

        return RateLimited(retry_after=worst_case[0],
                           reason_code=worst_case[1])