From 38c84a7f0b086f7b81697890f6b84c3605182573 Mon Sep 17 00:00:00 2001 From: Austin Donnelly Date: Mon, 3 Sep 2018 16:18:42 +0100 Subject: [PATCH 1/3] New test: 429 on first page of query results. --- test/retry_policy_tests.py | 34 +++++++++++++++++++++++++++++++++- 1 file changed, 33 insertions(+), 1 deletion(-) diff --git a/test/retry_policy_tests.py b/test/retry_policy_tests.py index bdff264..be477c7 100644 --- a/test/retry_policy_tests.py +++ b/test/retry_policy_tests.py @@ -1,4 +1,4 @@ -#The MIT License (MIT) +#The MIT License (MIT) #Copyright (c) 2014 Microsoft Corporation #Permission is hereby granted, free of charge, to any person obtaining a copy @@ -275,6 +275,38 @@ def test_default_retry_policy_for_create(self): retry_utility._ExecuteFunction = self.OriginalExecuteFunction + def test_429_on_first_page(self): + client = document_client.DocumentClient(Test_retry_policy_tests.host, {'masterKey': Test_retry_policy_tests.masterKey}) + + document_definition = { 'id': 'doc429', + 'name': 'sample document', + 'key': 'value'} + + created_document = client.CreateDocument(self.created_collection['_self'], document_definition) + + # Mock an overloaded server which always returns 429 Too Many + # Requests, by hooking the client's POST method. + original_post_function = client._DocumentClient__Post + client._DocumentClient__Post = self._MockPost429 + + # Test: query for the document. Expect the mock overloaded server + # to raise a 429 exception. + try: + query = client.QueryDocuments(self.created_collection['_self'], "SELECT * FROM c") + docs = list(query) # force execution now + self.assertFalse(True, 'function should raise HTTPFailure.') + + except errors.HTTPFailure as ex: + self.assertEqual(ex.status_code, StatusCodes.TOO_MANY_REQUESTS) + + client._DocumentClient__Post = original_post_function + client.DeleteDocument(created_doc['_self']) + + def _MockPost429(self, url, path, body, headers): + raise errors.HTTPFailure(StatusCodes.TOO_MANY_REQUESTS, + "Request rate is too large", + {HttpHeaders.RetryAfterInMilliseconds: 500}) + def _MockExecuteFunction(self, function, *args, **kwargs): raise errors.HTTPFailure(StatusCodes.TOO_MANY_REQUESTS, "Request rate is too large", {HttpHeaders.RetryAfterInMilliseconds: self.retry_after_in_milliseconds}) From ff3c66ee4b251acf5e18a2670ec6a8190f919c02 Mon Sep 17 00:00:00 2001 From: Austin Donnelly Date: Tue, 4 Sep 2018 11:25:31 +0100 Subject: [PATCH 2/3] Fix bug, and fix tests to match. --- .../execution_context/base_execution_context.py | 14 ++++---------- test/retry_policy_tests.py | 4 ++-- 2 files changed, 6 insertions(+), 12 deletions(-) diff --git a/pydocumentdb/execution_context/base_execution_context.py b/pydocumentdb/execution_context/base_execution_context.py index 5d17494..1cf9afa 100644 --- a/pydocumentdb/execution_context/base_execution_context.py +++ b/pydocumentdb/execution_context/base_execution_context.py @@ -124,13 +124,7 @@ def _fetch_items_helper_no_retries(self, fetch_function): if fetched_items: break return fetched_items - - def _fetch_items_helper_with_retries(self, fetch_function): - def callback(): - return self._fetch_items_helper_no_retries(fetch_function) - - return retry_utility._Execute(self._client, self._client._global_endpoint_manager, callback) - + class _DefaultQueryExecutionContext(_QueryExecutionContextBase): """ @@ -155,7 +149,7 @@ def __init__(self, client, options, fetch_function): def _fetch_next_block(self): while super(self.__class__, self)._has_more_pages() and len(self._buffer) == 0: - return self._fetch_items_helper_with_retries(self._fetch_function) + return self._fetch_items_helper_no_retries(self._fetch_function) class _MultiCollectionQueryExecutionContext(_QueryExecutionContextBase): """ @@ -224,7 +218,7 @@ def _fetch_next_block(self): :rtype: list """ # Fetch next block of results by executing the query against the current document collection - fetched_items = self._fetch_items_helper_with_retries(self._fetch_function) + fetched_items = self._fetch_items_helper_no_retries(self._fetch_function) # If there are multiple document collections to query for(in case of partitioning), keep looping through each one of them, # creating separate feed queries for each collection and fetching the items @@ -244,7 +238,7 @@ def fetch_fn(options): self._fetch_function = fetch_fn - fetched_items = self._fetch_items_helper_with_retries(self._fetch_function) + fetched_items = self._fetch_items_helper_no_retries(self._fetch_function) self._current_collection_index += 1 else: break diff --git a/test/retry_policy_tests.py b/test/retry_policy_tests.py index be477c7..849c01a 100644 --- a/test/retry_policy_tests.py +++ b/test/retry_policy_tests.py @@ -221,7 +221,7 @@ def test_default_retry_policy_for_query(self): result_docs = list(docs) self.assertEqual(result_docs[0]['id'], 'doc1') self.assertEqual(result_docs[1]['id'], 'doc2') - self.assertEqual(self.counter, 12) + self.assertEqual(self.counter, 6) self.counter = 0; retry_utility._ExecuteFunction = self.OriginalExecuteFunction @@ -300,7 +300,7 @@ def test_429_on_first_page(self): self.assertEqual(ex.status_code, StatusCodes.TOO_MANY_REQUESTS) client._DocumentClient__Post = original_post_function - client.DeleteDocument(created_doc['_self']) + client.DeleteDocument(created_document['_self']) def _MockPost429(self, url, path, body, headers): raise errors.HTTPFailure(StatusCodes.TOO_MANY_REQUESTS, From 55fabdd8b297a8a1be2e93e0a64b64943430e09a Mon Sep 17 00:00:00 2001 From: Austin Donnelly Date: Tue, 4 Sep 2018 12:05:47 +0100 Subject: [PATCH 3/3] Increase default retries. --- pydocumentdb/retry_options.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pydocumentdb/retry_options.py b/pydocumentdb/retry_options.py index 4557323..ab03b22 100644 --- a/pydocumentdb/retry_options.py +++ b/pydocumentdb/retry_options.py @@ -32,7 +32,7 @@ class RetryOptions(object): :ivar int MaxWaitTimeInSeconds: Max wait time in seconds to wait for a request while the retries are happening. Default value 30 seconds. """ - def __init__(self, max_retry_attempt_count = 9, fixed_retry_interval_in_milliseconds = None, max_wait_time_in_seconds = 30): + def __init__(self, max_retry_attempt_count = 17, fixed_retry_interval_in_milliseconds = None, max_wait_time_in_seconds = 60): self._max_retry_attempt_count = max_retry_attempt_count self._fixed_retry_interval_in_milliseconds = fixed_retry_interval_in_milliseconds self._max_wait_time_in_seconds = max_wait_time_in_seconds @@ -47,4 +47,4 @@ def FixedRetryIntervalInMilliseconds(self): @property def MaxWaitTimeInSeconds(self): - return self._max_wait_time_in_seconds \ No newline at end of file + return self._max_wait_time_in_seconds