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])
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()
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()
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])