async def test_backoff_calculation(): client = Client(user_config=CLIENT_CONFIG) response_429 = (await mocks.mock_GET_HTTP_Client_response_429())[1] # ^ has a 1 second difference in retry and datetime # backoff should be 2 by Okta standards retry_limit_reset = float(response_429.headers["X-Rate-Limit-Reset"]) date_time = convert_date_time_to_seconds(response_429.headers["Date"]) backoff_time = client.get_request_executor().calculate_backoff( retry_limit_reset, date_time) assert (backoff_time == 2)
async def fire_request_helper(self, request, attempts, request_start_time): """ Helper method to perform HTTP call with retries if needed Args: request (dict): HTTP request representation attempts (int): number of attempted HTTP calls so far request_start_time (float): original start time of request Returns: aiohttp.RequestInfo, aiohttp.ClientResponse, json, Exception: Tuple of request, response object, response json, and error if raised """ # Get start request time current_req_start_time = time.time() max_retries = self._max_retries req_timeout = self._request_timeout if req_timeout > 0 and \ (current_req_start_time - request_start_time) > req_timeout: # Timeout is hit for request return (None, None, None, Exception("Request Timeout exceeded.")) # Execute request _, res_details, resp_body, error = \ await self._http_client.send_request(request) # return immediately if request failed to launch (e.g. network is down, thus res_details is None) if res_details is None: return (None, None, None, error) headers = res_details.headers if attempts < max_retries and self.is_retryable_status(res_details.status): date_time = headers.get("Date", "") if date_time: date_time = convert_date_time_to_seconds(date_time) retry_limit_reset_headers = list(map(float, headers.getall( "X-Rate-Limit-Reset", []))) # header might be in lowercase, so check this too retry_limit_reset_headers.extend(list(map(float, headers.getall( "x-rate-limit-reset", [])))) retry_limit_reset = min(retry_limit_reset_headers) if len( retry_limit_reset_headers) > 0 else None if not date_time or not retry_limit_reset: return (None, res_details, resp_body, Exception( ERROR_MESSAGE_429_MISSING_DATE_X_RESET )) check_429 = self.is_too_many_requests(res_details.status, resp_body) if check_429: # backoff backoff_seconds = self.calculate_backoff( retry_limit_reset, date_time) logger.info(f'Hit rate limit. Retry request in {backoff_seconds} seconds.') logger.debug(f'Value of retry_limit_reset: {retry_limit_reset}') logger.debug(f'Value of date_time: {date_time}') self.pause_for_backoff(backoff_seconds) if (current_req_start_time + backoff_seconds)\ - request_start_time > req_timeout and req_timeout > 0: return (None, res_details, resp_body, resp_body) # Setup retry request attempts += 1 request['headers'].update( { RequestExecutor.RETRY_FOR_HEADER: headers.get( "X-Okta-Request-Id", ""), RequestExecutor.RETRY_COUNT_HEADER: str(attempts) } ) _, res_details, resp_body, error = await self.fire_request_helper( request, attempts, request_start_time ) if error: return (None, res_details, resp_body, error) return (request, res_details, resp_body, error)
async def fire_request_helper(self, request, attempts, request_start_time): """ Helper method to perform HTTP call with retries if needed Args: request (dict): HTTP request representation attempts (int): number of attempted HTTP calls so far request_start_time (float): original start time of request Returns: aiohttp.RequestInfo, aiohttp.ClientResponse, json, Exception: Tuple of request, response object, response json, and error if raised """ # Get start request time current_req_start_time = time.time() max_retries = self._max_retries req_timeout = self._request_timeout if req_timeout > 0 and \ (current_req_start_time - request_start_time) > req_timeout: # Timeout is hit for request return (None, None, None, Exception("Request Timeout exceeded.")) # Execute request _, res_details, resp_body, error = \ await self._http_client.send_request(request) check_429 = self.is_too_many_requests(res_details.status, resp_body) headers = res_details.headers if attempts < max_retries and (error or check_429): date_time = headers.get("Date", "") if date_time: date_time = convert_date_time_to_seconds(date_time) retry_limit_reset_headers = list( map(float, headers.getall("X-Rate-Limit-Reset", []))) retry_limit_reset = min(retry_limit_reset_headers) if len( retry_limit_reset_headers) > 0 else None if not date_time or not retry_limit_reset: return (None, res_details, resp_body, Exception(ERROR_MESSAGE_429_MISSING_DATE_X_RESET)) if check_429: # backoff backoff_seconds = self.calculate_backoff( retry_limit_reset, date_time) self.pause_for_backoff(backoff_seconds) if (current_req_start_time + backoff_seconds)\ - request_start_time > req_timeout and req_timeout > 0: return (None, res_details, resp_body, resp_body) # Setup retry request attempts += 1 request['headers'].update({ RequestExecutor.RETRY_FOR_HEADER: headers.get("X-Okta-Request-Id", ""), RequestExecutor.RETRY_COUNT_HEADER: attempts }) _, res_details, resp_body, error = await self.fire_request_helper( request, attempts, request_start_time) if error: return (None, res_details, resp_body, error) return (request, res_details, resp_body, error)