From 824665d1a3bb3bb59f0625dc3c269e0110a09bcc Mon Sep 17 00:00:00 2001 From: annatisch Date: Mon, 5 Apr 2021 18:26:13 -0700 Subject: [PATCH] [Tables] Fix for async retry policy (#17810) * Fix bug in async retry policy * Added some retry testing * Updated release notes * Review feedback * Moved static methods --- sdk/tables/azure-data-tables/CHANGELOG.md | 1 + .../azure/data/tables/_policies.py | 151 +++++++------- .../azure/data/tables/aio/_policies_async.py | 11 +- .../data/tables/aio/_table_client_async.py | 3 +- .../tests/_shared/testcase.py | 27 +++ ...test_retry_callback_and_retry_context.yaml | 180 +++++++++++++++++ ...test_retry.test_retry_on_server_error.yaml | 180 +++++++++++++++++ ...st_retry.test_retry_on_socket_timeout.yaml | 85 ++++++++ .../test_retry.test_retry_on_timeout.yaml | 180 +++++++++++++++++ ...etry_callback_and_retry_context_async.yaml | 136 +++++++++++++ ...sync.test_retry_on_server_error_async.yaml | 136 +++++++++++++ ...nc.test_retry_on_socket_timeout_async.yaml | 62 ++++++ ...try_async.test_retry_on_timeout_async.yaml | 136 +++++++++++++ .../azure-data-tables/tests/test_retry.py | 187 ++++++++++++++++++ .../tests/test_retry_async.py | 187 ++++++++++++++++++ 15 files changed, 1580 insertions(+), 82 deletions(-) create mode 100644 sdk/tables/azure-data-tables/tests/recordings/test_retry.test_retry_callback_and_retry_context.yaml create mode 100644 sdk/tables/azure-data-tables/tests/recordings/test_retry.test_retry_on_server_error.yaml create mode 100644 sdk/tables/azure-data-tables/tests/recordings/test_retry.test_retry_on_socket_timeout.yaml create mode 100644 sdk/tables/azure-data-tables/tests/recordings/test_retry.test_retry_on_timeout.yaml create mode 100644 sdk/tables/azure-data-tables/tests/recordings/test_retry_async.test_retry_callback_and_retry_context_async.yaml create mode 100644 sdk/tables/azure-data-tables/tests/recordings/test_retry_async.test_retry_on_server_error_async.yaml create mode 100644 sdk/tables/azure-data-tables/tests/recordings/test_retry_async.test_retry_on_socket_timeout_async.yaml create mode 100644 sdk/tables/azure-data-tables/tests/recordings/test_retry_async.test_retry_on_timeout_async.yaml create mode 100644 sdk/tables/azure-data-tables/tests/test_retry.py create mode 100644 sdk/tables/azure-data-tables/tests/test_retry_async.py diff --git a/sdk/tables/azure-data-tables/CHANGELOG.md b/sdk/tables/azure-data-tables/CHANGELOG.md index 32511251143a..e1e32298ca43 100644 --- a/sdk/tables/azure-data-tables/CHANGELOG.md +++ b/sdk/tables/azure-data-tables/CHANGELOG.md @@ -3,6 +3,7 @@ ## 12.0.0b6 (2021-04-06) * Updated deserialization of datetime fields in entities to support preservation of the service format with additional decimal place. * Passing a string parameter into a query filter will now be escaped to protect against injection. +* Fixed bug in incrementing retries in async retry policy ## 12.0.0b5 (2021-03-09) * This version and all future versions will require Python 2.7 or Python 3.6+, Python 3.5 is no longer supported. diff --git a/sdk/tables/azure-data-tables/azure/data/tables/_policies.py b/sdk/tables/azure-data-tables/azure/data/tables/_policies.py index 17cfa2776052..bba12e27acfd 100644 --- a/sdk/tables/azure-data-tables/azure/data/tables/_policies.py +++ b/sdk/tables/azure-data-tables/azure/data/tables/_policies.py @@ -108,6 +108,78 @@ def is_retry(response, mode): return True return False +def set_next_host_location(settings, request): + """ + A function which sets the next host location on the request, if applicable. + + :param ~azure.storage.models.RetryContext context: + The retry context containing the previous host location and the request + to evaluate and possibly modify. + """ + if settings["hosts"] and all(settings["hosts"].values()): + url = urlparse(request.url) + # If there's more than one possible location, retry to the alternative + if settings["mode"] == LocationMode.PRIMARY: + settings["mode"] = LocationMode.SECONDARY + else: + settings["mode"] = LocationMode.PRIMARY + updated = url._replace(netloc=settings["hosts"].get(settings["mode"])) + request.url = updated.geturl() + +def increment(settings, request, response=None, error=None): + """Increment the retry counters. + + :param Any request: + :param dict settings: + :param Any response: A pipeline response object. + :param Any error: An error encountered during the request, or + None if the response was received successfully. + :keyword callable cls: A custom type or function that will be passed the direct response + :return: Whether the retry attempts are exhausted. + :rtype: None + """ + settings["total"] -= 1 + + if error and isinstance(error, ServiceRequestError): + # Errors when we're fairly sure that the server did not receive the + # request, so it should be safe to retry. + settings["connect"] -= 1 + settings["history"].append(RequestHistory(request, error=error)) + + elif error and isinstance(error, ServiceResponseError): + # Errors that occur after the request has been started, so we should + # assume that the server began processing it. + settings["read"] -= 1 + settings["history"].append(RequestHistory(request, error=error)) + + else: + # Incrementing because of a server error like a 500 in + # status_forcelist and a the given method is in the whitelist + if response: + settings["status"] -= 1 + settings["history"].append( + RequestHistory(request, http_response=response) + ) + + if not is_exhausted(settings): + if request.method not in ["PUT"] and settings["retry_secondary"]: + set_next_host_location(settings, request) + + # rewind the request body if it is a stream + if request.body and hasattr(request.body, "read"): + # no position was saved, then retry would not work + if settings["body_position"] is None: + return False + try: + # attempt to rewind the body to the initial position + request.body.seek(settings["body_position"], SEEK_SET) + except (UnsupportedOperation, ValueError): + # if body is not seekable, then retry would not work + return False + settings["count"] += 1 + return True + return False + def urljoin(base_url, stub_url): parsed = urlparse(base_url) @@ -464,24 +536,6 @@ def get_backoff_time(self, settings): random_range_end = backoff + self.random_jitter_range return random_generator.uniform(random_range_start, random_range_end) - def _set_next_host_location(self, settings, request): # pylint: disable=no-self-use - """ - A function which sets the next host location on the request, if applicable. - - :param ~azure.storage.models.RetryContext context: - The retry context containing the previous host location and the request - to evaluate and possibly modify. - """ - if settings["hosts"] and all(settings["hosts"].values()): - url = urlparse(request.url) - # If there's more than one possible location, retry to the alternative - if settings["mode"] == LocationMode.PRIMARY: - settings["mode"] = LocationMode.SECONDARY - else: - settings["mode"] = LocationMode.PRIMARY - updated = url._replace(netloc=settings["hosts"].get(settings["mode"])) - request.url = updated.geturl() - def configure_retries( self, request ): # pylint: disable=no-self-use, arguments-differ @@ -530,63 +584,6 @@ def sleep(self, settings, transport): # pylint: disable=arguments-differ return transport.sleep(backoff) - def increment( - self, settings, request, response=None, error=None, **kwargs - ): # pylint: disable=unused-argument, arguments-differ - # type: (...)->None - """Increment the retry counters. - - :param Any request: - :param dict settings: - :param Any response: A pipeline response object. - :param Any error: An error encountered during the request, or - None if the response was received successfully. - :keyword callable cls: A custom type or function that will be passed the direct response - :return: Whether the retry attempts are exhausted. - :rtype: None - """ - settings["total"] -= 1 - - if error and isinstance(error, ServiceRequestError): - # Errors when we're fairly sure that the server did not receive the - # request, so it should be safe to retry. - settings["connect"] -= 1 - settings["history"].append(RequestHistory(request, error=error)) - - elif error and isinstance(error, ServiceResponseError): - # Errors that occur after the request has been started, so we should - # assume that the server began processing it. - settings["read"] -= 1 - settings["history"].append(RequestHistory(request, error=error)) - - else: - # Incrementing because of a server error like a 500 in - # status_forcelist and a the given method is in the whitelist - if response: - settings["status"] -= 1 - settings["history"].append( - RequestHistory(request, http_response=response) - ) - - if not is_exhausted(settings): - if request.method not in ["PUT"] and settings["retry_secondary"]: - self._set_next_host_location(settings, request) - - # rewind the request body if it is a stream - if request.body and hasattr(request.body, "read"): - # no position was saved, then retry would not work - if settings["body_position"] is None: - return False - try: - # attempt to rewind the body to the initial position - request.body.seek(settings["body_position"], SEEK_SET) - except (UnsupportedOperation, ValueError): - # if body is not seekable, then retry would not work - return False - settings["count"] += 1 - return True - return False - def send(self, request): """ :param Any request: @@ -599,7 +596,7 @@ def send(self, request): try: response = self.next.send(request) if is_retry(response, retry_settings["mode"]): - retries_remaining = self.increment( + retries_remaining = increment( retry_settings, request=request.http_request, response=response.http_response, @@ -615,7 +612,7 @@ def send(self, request): continue break except AzureError as err: - retries_remaining = self.increment( + retries_remaining = increment( retry_settings, request=request.http_request, error=err ) if retries_remaining: diff --git a/sdk/tables/azure-data-tables/azure/data/tables/aio/_policies_async.py b/sdk/tables/azure-data-tables/azure/data/tables/aio/_policies_async.py index c932a4102737..89ff936c4539 100644 --- a/sdk/tables/azure-data-tables/azure/data/tables/aio/_policies_async.py +++ b/sdk/tables/azure-data-tables/azure/data/tables/aio/_policies_async.py @@ -12,7 +12,7 @@ from azure.core.pipeline.policies import AsyncHTTPPolicy, AsyncRetryPolicy from azure.core.exceptions import AzureError -from .._policies import is_retry, TablesRetryPolicy +from .._policies import is_retry, increment, TablesRetryPolicy if TYPE_CHECKING: from azure.core.pipeline import PipelineRequest, PipelineResponse @@ -168,8 +168,10 @@ async def send(self, request): try: response = await self.next.send(request) if is_retry(response, retry_settings["mode"]): - retries_remaining = self.increment( - retry_settings, response=response.http_response + retries_remaining = increment( + retry_settings, + request=request.http_request, + response=response.http_response ) if retries_remaining: await retry_hook( @@ -182,7 +184,8 @@ async def send(self, request): continue break except AzureError as err: - retries_remaining = self.increment(retry_settings, error=err) + retries_remaining = increment( + retry_settings, request=request.http_request, error=err) if retries_remaining: await retry_hook( retry_settings, diff --git a/sdk/tables/azure-data-tables/azure/data/tables/aio/_table_client_async.py b/sdk/tables/azure-data-tables/azure/data/tables/aio/_table_client_async.py index 0f0597191cb3..0de32df89018 100644 --- a/sdk/tables/azure-data-tables/azure/data/tables/aio/_table_client_async.py +++ b/sdk/tables/azure-data-tables/azure/data/tables/aio/_table_client_async.py @@ -241,11 +241,12 @@ async def create_table( :dedent: 8 :caption: Creating a table from the TableClient object. """ - table_properties = TableProperties(table_name=self.table_name, **kwargs) + table_properties = TableProperties(table_name=self.table_name) try: metadata, _ = await self._client.table.create( table_properties, cls=kwargs.pop("cls", _return_headers_and_deserialized), + **kwargs ) return _trim_service_metadata(metadata) except HttpResponseError as error: diff --git a/sdk/tables/azure-data-tables/tests/_shared/testcase.py b/sdk/tables/azure-data-tables/tests/_shared/testcase.py index baec5c2ca926..518aead71473 100644 --- a/sdk/tables/azure-data-tables/tests/_shared/testcase.py +++ b/sdk/tables/azure-data-tables/tests/_shared/testcase.py @@ -71,3 +71,30 @@ def generate_sas_token(self): def generate_fake_token(self): return FakeTokenCredential() + + +class ResponseCallback(object): + def __init__(self, status=None, new_status=None): + self.status = status + self.new_status = new_status + self.first = True + self.count = 0 + + def override_first_status(self, response): + if self.first and response.http_response.status_code == self.status: + response.http_response.status_code = self.new_status + self.first = False + self.count += 1 + + def override_status(self, response): + if response.http_response.status_code == self.status: + response.http_response.status_code = self.new_status + self.count += 1 + + +class RetryCounter(object): + def __init__(self): + self.count = 0 + + def simple_count(self, retry_context): + self.count += 1 diff --git a/sdk/tables/azure-data-tables/tests/recordings/test_retry.test_retry_callback_and_retry_context.yaml b/sdk/tables/azure-data-tables/tests/recordings/test_retry.test_retry_callback_and_retry_context.yaml new file mode 100644 index 000000000000..3adbd15a00b7 --- /dev/null +++ b/sdk/tables/azure-data-tables/tests/recordings/test_retry.test_retry_callback_and_retry_context.yaml @@ -0,0 +1,180 @@ +interactions: +- request: + body: '{"TableName": "uttablee75e13f0"}' + headers: + Accept: + - application/json;odata=minimalmetadata + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + Content-Length: + - '32' + Content-Type: + - application/json;odata=nometadata + DataServiceVersion: + - '3.0' + Date: + - Mon, 05 Apr 2021 20:52:56 GMT + User-Agent: + - azsdk-python-data-tables/12.0.0b6 Python/3.7.4 (Windows-10-10.0.19041-SP0) + x-ms-date: + - Mon, 05 Apr 2021 20:52:56 GMT + x-ms-version: + - '2019-02-02' + method: POST + uri: https://fake_table_account.table.core.windows.net/Tables + response: + body: + string: '{"odata.metadata":"https://fake_table_account.table.core.windows.net/$metadata#Tables/@Element","TableName":"uttablee75e13f0"}' + headers: + cache-control: + - no-cache + content-type: + - application/json;odata=minimalmetadata;streaming=true;charset=utf-8 + date: + - Mon, 05 Apr 2021 20:52:59 GMT + location: + - https://fake_table_account.table.core.windows.net/Tables('uttablee75e13f0') + server: + - Windows-Azure-Table/1.0 Microsoft-HTTPAPI/2.0 + transfer-encoding: + - chunked + x-content-type-options: + - nosniff + x-ms-version: + - '2019-02-02' + status: + code: 201 + message: Created +- request: + body: '{"TableName": "uttablee75e13f0"}' + headers: + Accept: + - application/json;odata=minimalmetadata + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + Content-Length: + - '32' + Content-Type: + - application/json;odata=nometadata + DataServiceVersion: + - '3.0' + Date: + - Mon, 05 Apr 2021 20:52:56 GMT + User-Agent: + - azsdk-python-data-tables/12.0.0b6 Python/3.7.4 (Windows-10-10.0.19041-SP0) + x-ms-date: + - Mon, 05 Apr 2021 20:52:56 GMT + x-ms-version: + - '2019-02-02' + method: POST + uri: https://fake_table_account.table.core.windows.net/Tables + response: + body: + string: '{"odata.error":{"code":"TableAlreadyExists","message":{"lang":"en-US","value":"The + table specified already exists.\nRequestId:3a9bd1f0-0002-000c-7e5d-2a4d2c000000\nTime:2021-04-05T20:53:02.8814645Z"}}}' + headers: + cache-control: + - no-cache + content-type: + - application/json;odata=minimalmetadata;streaming=true;charset=utf-8 + date: + - Mon, 05 Apr 2021 20:53:02 GMT + server: + - Windows-Azure-Table/1.0 Microsoft-HTTPAPI/2.0 + transfer-encoding: + - chunked + x-content-type-options: + - nosniff + x-ms-version: + - '2019-02-02' + status: + code: 409 + message: Conflict +- request: + body: null + headers: + Accept: + - application/json + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + Content-Length: + - '0' + Date: + - Mon, 05 Apr 2021 20:53:03 GMT + User-Agent: + - azsdk-python-data-tables/12.0.0b6 Python/3.7.4 (Windows-10-10.0.19041-SP0) + x-ms-date: + - Mon, 05 Apr 2021 20:53:03 GMT + x-ms-version: + - '2019-02-02' + method: DELETE + uri: https://fake_table_account.table.core.windows.net/Tables('uttablee75e13f0') + response: + body: + string: '' + headers: + cache-control: + - no-cache + content-length: + - '0' + date: + - Mon, 05 Apr 2021 20:53:03 GMT + server: + - Windows-Azure-Table/1.0 Microsoft-HTTPAPI/2.0 + x-content-type-options: + - nosniff + x-ms-version: + - '2019-02-02' + status: + code: 204 + message: No Content +- request: + body: null + headers: + Accept: + - application/json + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + Content-Length: + - '0' + Date: + - Mon, 05 Apr 2021 20:53:03 GMT + User-Agent: + - azsdk-python-data-tables/12.0.0b6 Python/3.7.4 (Windows-10-10.0.19041-SP0) + x-ms-date: + - Mon, 05 Apr 2021 20:53:03 GMT + x-ms-version: + - '2019-02-02' + method: DELETE + uri: https://fake_table_account.table.core.windows.net/Tables('uttablee75e13f0') + response: + body: + string: '{"odata.error":{"code":"ResourceNotFound","message":{"lang":"en-US","value":"The + specified resource does not exist.\nRequestId:3a9bd24c-0002-000c-4e5d-2a4d2c000000\nTime:2021-04-05T20:53:03.7921084Z"}}}' + headers: + cache-control: + - no-cache + content-type: + - application/json;odata=minimalmetadata;streaming=true;charset=utf-8 + date: + - Mon, 05 Apr 2021 20:53:03 GMT + server: + - Windows-Azure-Table/1.0 Microsoft-HTTPAPI/2.0 + transfer-encoding: + - chunked + x-content-type-options: + - nosniff + x-ms-version: + - '2019-02-02' + status: + code: 404 + message: Not Found +version: 1 diff --git a/sdk/tables/azure-data-tables/tests/recordings/test_retry.test_retry_on_server_error.yaml b/sdk/tables/azure-data-tables/tests/recordings/test_retry.test_retry_on_server_error.yaml new file mode 100644 index 000000000000..d2175576adb0 --- /dev/null +++ b/sdk/tables/azure-data-tables/tests/recordings/test_retry.test_retry_on_server_error.yaml @@ -0,0 +1,180 @@ +interactions: +- request: + body: '{"TableName": "uttable270d0f94"}' + headers: + Accept: + - application/json;odata=minimalmetadata + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + Content-Length: + - '32' + Content-Type: + - application/json;odata=nometadata + DataServiceVersion: + - '3.0' + Date: + - Mon, 05 Apr 2021 20:53:04 GMT + User-Agent: + - azsdk-python-data-tables/12.0.0b6 Python/3.7.4 (Windows-10-10.0.19041-SP0) + x-ms-date: + - Mon, 05 Apr 2021 20:53:04 GMT + x-ms-version: + - '2019-02-02' + method: POST + uri: https://fake_table_account.table.core.windows.net/Tables + response: + body: + string: '{"odata.metadata":"https://fake_table_account.table.core.windows.net/$metadata#Tables/@Element","TableName":"uttable270d0f94"}' + headers: + cache-control: + - no-cache + content-type: + - application/json;odata=minimalmetadata;streaming=true;charset=utf-8 + date: + - Mon, 05 Apr 2021 20:53:04 GMT + location: + - https://fake_table_account.table.core.windows.net/Tables('uttable270d0f94') + server: + - Windows-Azure-Table/1.0 Microsoft-HTTPAPI/2.0 + transfer-encoding: + - chunked + x-content-type-options: + - nosniff + x-ms-version: + - '2019-02-02' + status: + code: 201 + message: Created +- request: + body: '{"TableName": "uttable270d0f94"}' + headers: + Accept: + - application/json;odata=minimalmetadata + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + Content-Length: + - '32' + Content-Type: + - application/json;odata=nometadata + DataServiceVersion: + - '3.0' + Date: + - Mon, 05 Apr 2021 20:53:04 GMT + User-Agent: + - azsdk-python-data-tables/12.0.0b6 Python/3.7.4 (Windows-10-10.0.19041-SP0) + x-ms-date: + - Mon, 05 Apr 2021 20:53:04 GMT + x-ms-version: + - '2019-02-02' + method: POST + uri: https://fake_table_account.table.core.windows.net/Tables + response: + body: + string: '{"odata.error":{"code":"TableAlreadyExists","message":{"lang":"en-US","value":"The + table specified already exists.\nRequestId:71db8f83-4002-0032-1b5d-2ada53000000\nTime:2021-04-05T20:53:22.8853531Z"}}}' + headers: + cache-control: + - no-cache + content-type: + - application/json;odata=minimalmetadata;streaming=true;charset=utf-8 + date: + - Mon, 05 Apr 2021 20:53:22 GMT + server: + - Windows-Azure-Table/1.0 Microsoft-HTTPAPI/2.0 + transfer-encoding: + - chunked + x-content-type-options: + - nosniff + x-ms-version: + - '2019-02-02' + status: + code: 409 + message: Conflict +- request: + body: null + headers: + Accept: + - application/json + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + Content-Length: + - '0' + Date: + - Mon, 05 Apr 2021 20:53:23 GMT + User-Agent: + - azsdk-python-data-tables/12.0.0b6 Python/3.7.4 (Windows-10-10.0.19041-SP0) + x-ms-date: + - Mon, 05 Apr 2021 20:53:23 GMT + x-ms-version: + - '2019-02-02' + method: DELETE + uri: https://fake_table_account.table.core.windows.net/Tables('uttable270d0f94') + response: + body: + string: '' + headers: + cache-control: + - no-cache + content-length: + - '0' + date: + - Mon, 05 Apr 2021 20:53:22 GMT + server: + - Windows-Azure-Table/1.0 Microsoft-HTTPAPI/2.0 + x-content-type-options: + - nosniff + x-ms-version: + - '2019-02-02' + status: + code: 204 + message: No Content +- request: + body: null + headers: + Accept: + - application/json + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + Content-Length: + - '0' + Date: + - Mon, 05 Apr 2021 20:53:23 GMT + User-Agent: + - azsdk-python-data-tables/12.0.0b6 Python/3.7.4 (Windows-10-10.0.19041-SP0) + x-ms-date: + - Mon, 05 Apr 2021 20:53:23 GMT + x-ms-version: + - '2019-02-02' + method: DELETE + uri: https://fake_table_account.table.core.windows.net/Tables('uttable270d0f94') + response: + body: + string: '{"odata.error":{"code":"ResourceNotFound","message":{"lang":"en-US","value":"The + specified resource does not exist.\nRequestId:71db9005-4002-0032-195d-2ada53000000\nTime:2021-04-05T20:53:23.8910718Z"}}}' + headers: + cache-control: + - no-cache + content-type: + - application/json;odata=minimalmetadata;streaming=true;charset=utf-8 + date: + - Mon, 05 Apr 2021 20:53:23 GMT + server: + - Windows-Azure-Table/1.0 Microsoft-HTTPAPI/2.0 + transfer-encoding: + - chunked + x-content-type-options: + - nosniff + x-ms-version: + - '2019-02-02' + status: + code: 404 + message: Not Found +version: 1 diff --git a/sdk/tables/azure-data-tables/tests/recordings/test_retry.test_retry_on_socket_timeout.yaml b/sdk/tables/azure-data-tables/tests/recordings/test_retry.test_retry_on_socket_timeout.yaml new file mode 100644 index 000000000000..2b84cc03df6c --- /dev/null +++ b/sdk/tables/azure-data-tables/tests/recordings/test_retry.test_retry_on_socket_timeout.yaml @@ -0,0 +1,85 @@ +interactions: +- request: + body: null + headers: + Accept: + - application/json + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + Content-Length: + - '0' + Date: + - Mon, 05 Apr 2021 20:53:34 GMT + User-Agent: + - azsdk-python-data-tables/12.0.0b6 Python/3.7.4 (Windows-10-10.0.19041-SP0) + x-ms-date: + - Mon, 05 Apr 2021 20:53:34 GMT + x-ms-version: + - '2019-02-02' + method: DELETE + uri: https://fake_table_account.table.core.windows.net/Tables('uttable46e31063') + response: + body: + string: '' + headers: + cache-control: + - no-cache + content-length: + - '0' + date: + - Mon, 05 Apr 2021 20:53:34 GMT + server: + - Windows-Azure-Table/1.0 Microsoft-HTTPAPI/2.0 + x-content-type-options: + - nosniff + x-ms-version: + - '2019-02-02' + status: + code: 204 + message: No Content +- request: + body: null + headers: + Accept: + - application/json + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + Content-Length: + - '0' + Date: + - Mon, 05 Apr 2021 20:53:34 GMT + User-Agent: + - azsdk-python-data-tables/12.0.0b6 Python/3.7.4 (Windows-10-10.0.19041-SP0) + x-ms-date: + - Mon, 05 Apr 2021 20:53:34 GMT + x-ms-version: + - '2019-02-02' + method: DELETE + uri: https://fake_table_account.table.core.windows.net/Tables('uttable46e31063') + response: + body: + string: '{"odata.error":{"code":"ResourceNotFound","message":{"lang":"en-US","value":"The + specified resource does not exist.\nRequestId:05291b19-e002-0004-345d-2a5723000000\nTime:2021-04-05T20:53:34.4934432Z"}}}' + headers: + cache-control: + - no-cache + content-type: + - application/json;odata=minimalmetadata;streaming=true;charset=utf-8 + date: + - Mon, 05 Apr 2021 20:53:34 GMT + server: + - Windows-Azure-Table/1.0 Microsoft-HTTPAPI/2.0 + transfer-encoding: + - chunked + x-content-type-options: + - nosniff + x-ms-version: + - '2019-02-02' + status: + code: 404 + message: Not Found +version: 1 diff --git a/sdk/tables/azure-data-tables/tests/recordings/test_retry.test_retry_on_timeout.yaml b/sdk/tables/azure-data-tables/tests/recordings/test_retry.test_retry_on_timeout.yaml new file mode 100644 index 000000000000..65e4b858750d --- /dev/null +++ b/sdk/tables/azure-data-tables/tests/recordings/test_retry.test_retry_on_timeout.yaml @@ -0,0 +1,180 @@ +interactions: +- request: + body: '{"TableName": "uttabledd800d7b"}' + headers: + Accept: + - application/json;odata=minimalmetadata + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + Content-Length: + - '32' + Content-Type: + - application/json;odata=nometadata + DataServiceVersion: + - '3.0' + Date: + - Mon, 05 Apr 2021 20:53:34 GMT + User-Agent: + - azsdk-python-data-tables/12.0.0b6 Python/3.7.4 (Windows-10-10.0.19041-SP0) + x-ms-date: + - Mon, 05 Apr 2021 20:53:34 GMT + x-ms-version: + - '2019-02-02' + method: POST + uri: https://fake_table_account.table.core.windows.net/Tables + response: + body: + string: '{"odata.metadata":"https://fake_table_account.table.core.windows.net/$metadata#Tables/@Element","TableName":"uttabledd800d7b"}' + headers: + cache-control: + - no-cache + content-type: + - application/json;odata=minimalmetadata;streaming=true;charset=utf-8 + date: + - Mon, 05 Apr 2021 20:53:34 GMT + location: + - https://fake_table_account.table.core.windows.net/Tables('uttabledd800d7b') + server: + - Windows-Azure-Table/1.0 Microsoft-HTTPAPI/2.0 + transfer-encoding: + - chunked + x-content-type-options: + - nosniff + x-ms-version: + - '2019-02-02' + status: + code: 201 + message: Created +- request: + body: '{"TableName": "uttabledd800d7b"}' + headers: + Accept: + - application/json;odata=minimalmetadata + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + Content-Length: + - '32' + Content-Type: + - application/json;odata=nometadata + DataServiceVersion: + - '3.0' + Date: + - Mon, 05 Apr 2021 20:53:34 GMT + User-Agent: + - azsdk-python-data-tables/12.0.0b6 Python/3.7.4 (Windows-10-10.0.19041-SP0) + x-ms-date: + - Mon, 05 Apr 2021 20:53:34 GMT + x-ms-version: + - '2019-02-02' + method: POST + uri: https://fake_table_account.table.core.windows.net/Tables + response: + body: + string: '{"odata.error":{"code":"TableAlreadyExists","message":{"lang":"en-US","value":"The + table specified already exists.\nRequestId:f2519219-5002-0011-145d-2a4090000000\nTime:2021-04-05T20:53:52.6677367Z"}}}' + headers: + cache-control: + - no-cache + content-type: + - application/json;odata=minimalmetadata;streaming=true;charset=utf-8 + date: + - Mon, 05 Apr 2021 20:53:51 GMT + server: + - Windows-Azure-Table/1.0 Microsoft-HTTPAPI/2.0 + transfer-encoding: + - chunked + x-content-type-options: + - nosniff + x-ms-version: + - '2019-02-02' + status: + code: 409 + message: Conflict +- request: + body: null + headers: + Accept: + - application/json + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + Content-Length: + - '0' + Date: + - Mon, 05 Apr 2021 20:53:52 GMT + User-Agent: + - azsdk-python-data-tables/12.0.0b6 Python/3.7.4 (Windows-10-10.0.19041-SP0) + x-ms-date: + - Mon, 05 Apr 2021 20:53:52 GMT + x-ms-version: + - '2019-02-02' + method: DELETE + uri: https://fake_table_account.table.core.windows.net/Tables('uttabledd800d7b') + response: + body: + string: '' + headers: + cache-control: + - no-cache + content-length: + - '0' + date: + - Mon, 05 Apr 2021 20:53:52 GMT + server: + - Windows-Azure-Table/1.0 Microsoft-HTTPAPI/2.0 + x-content-type-options: + - nosniff + x-ms-version: + - '2019-02-02' + status: + code: 204 + message: No Content +- request: + body: null + headers: + Accept: + - application/json + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + Content-Length: + - '0' + Date: + - Mon, 05 Apr 2021 20:53:52 GMT + User-Agent: + - azsdk-python-data-tables/12.0.0b6 Python/3.7.4 (Windows-10-10.0.19041-SP0) + x-ms-date: + - Mon, 05 Apr 2021 20:53:52 GMT + x-ms-version: + - '2019-02-02' + method: DELETE + uri: https://fake_table_account.table.core.windows.net/Tables('uttabledd800d7b') + response: + body: + string: '{"odata.error":{"code":"ResourceNotFound","message":{"lang":"en-US","value":"The + specified resource does not exist.\nRequestId:f2519222-5002-0011-1c5d-2a4090000000\nTime:2021-04-05T20:53:52.8198466Z"}}}' + headers: + cache-control: + - no-cache + content-type: + - application/json;odata=minimalmetadata;streaming=true;charset=utf-8 + date: + - Mon, 05 Apr 2021 20:53:52 GMT + server: + - Windows-Azure-Table/1.0 Microsoft-HTTPAPI/2.0 + transfer-encoding: + - chunked + x-content-type-options: + - nosniff + x-ms-version: + - '2019-02-02' + status: + code: 404 + message: Not Found +version: 1 diff --git a/sdk/tables/azure-data-tables/tests/recordings/test_retry_async.test_retry_callback_and_retry_context_async.yaml b/sdk/tables/azure-data-tables/tests/recordings/test_retry_async.test_retry_callback_and_retry_context_async.yaml new file mode 100644 index 000000000000..9f8bc60d34c4 --- /dev/null +++ b/sdk/tables/azure-data-tables/tests/recordings/test_retry_async.test_retry_callback_and_retry_context_async.yaml @@ -0,0 +1,136 @@ +interactions: +- request: + body: '{"TableName": "uttablef7b718ea"}' + headers: + Accept: + - application/json;odata=minimalmetadata + Content-Length: + - '32' + Content-Type: + - application/json;odata=nometadata + DataServiceVersion: + - '3.0' + Date: + - Mon, 05 Apr 2021 21:51:08 GMT + User-Agent: + - azsdk-python-data-tables/12.0.0b6 Python/3.7.4 (Windows-10-10.0.19041-SP0) + x-ms-date: + - Mon, 05 Apr 2021 21:51:08 GMT + x-ms-version: + - '2019-02-02' + method: POST + uri: https://fake_table_account.table.core.windows.net/Tables + response: + body: + string: '{"odata.metadata":"https://fake_table_account.table.core.windows.net/$metadata#Tables/@Element","TableName":"uttablef7b718ea"}' + headers: + cache-control: no-cache + content-type: application/json;odata=minimalmetadata;streaming=true;charset=utf-8 + date: Mon, 05 Apr 2021 21:51:07 GMT + location: https://fake_table_account.table.core.windows.net/Tables('uttablef7b718ea') + server: Windows-Azure-Table/1.0 Microsoft-HTTPAPI/2.0 + transfer-encoding: chunked + x-content-type-options: nosniff + x-ms-version: '2019-02-02' + status: + code: 201 + message: Created + url: https://seankaneprim.table.core.windows.net/Tables +- request: + body: '{"TableName": "uttablef7b718ea"}' + headers: + Accept: + - application/json;odata=minimalmetadata + Content-Length: + - '32' + Content-Type: + - application/json;odata=nometadata + DataServiceVersion: + - '3.0' + Date: + - Mon, 05 Apr 2021 21:51:08 GMT + User-Agent: + - azsdk-python-data-tables/12.0.0b6 Python/3.7.4 (Windows-10-10.0.19041-SP0) + x-ms-date: + - Mon, 05 Apr 2021 21:51:08 GMT + x-ms-version: + - '2019-02-02' + method: POST + uri: https://fake_table_account.table.core.windows.net/Tables + response: + body: + string: '{"odata.error":{"code":"TableAlreadyExists","message":{"lang":"en-US","value":"The + table specified already exists.\nRequestId:c5dc7620-d002-00a6-0d65-2a6d3a000000\nTime:2021-04-05T21:51:29.0279106Z"}}}' + headers: + cache-control: no-cache + content-type: application/json;odata=minimalmetadata;streaming=true;charset=utf-8 + date: Mon, 05 Apr 2021 21:51:28 GMT + server: Windows-Azure-Table/1.0 Microsoft-HTTPAPI/2.0 + transfer-encoding: chunked + x-content-type-options: nosniff + x-ms-version: '2019-02-02' + status: + code: 409 + message: Conflict + url: https://seankaneprim.table.core.windows.net/Tables +- request: + body: null + headers: + Accept: + - application/json + Date: + - Mon, 05 Apr 2021 21:51:29 GMT + User-Agent: + - azsdk-python-data-tables/12.0.0b6 Python/3.7.4 (Windows-10-10.0.19041-SP0) + x-ms-date: + - Mon, 05 Apr 2021 21:51:29 GMT + x-ms-version: + - '2019-02-02' + method: DELETE + uri: https://fake_table_account.table.core.windows.net/Tables('uttablef7b718ea') + response: + body: + string: '' + headers: + cache-control: no-cache + content-length: '0' + date: Mon, 05 Apr 2021 21:51:28 GMT + server: Windows-Azure-Table/1.0 Microsoft-HTTPAPI/2.0 + x-content-type-options: nosniff + x-ms-version: '2019-02-02' + status: + code: 204 + message: No Content + url: https://seankaneprim.table.core.windows.net/Tables('uttablef7b718ea') +- request: + body: null + headers: + Accept: + - application/json + Date: + - Mon, 05 Apr 2021 21:51:29 GMT + User-Agent: + - azsdk-python-data-tables/12.0.0b6 Python/3.7.4 (Windows-10-10.0.19041-SP0) + x-ms-date: + - Mon, 05 Apr 2021 21:51:29 GMT + x-ms-version: + - '2019-02-02' + method: DELETE + uri: https://fake_table_account.table.core.windows.net/Tables('uttablef7b718ea') + response: + body: + string: '{"odata.error":{"code":"ResourceNotFound","message":{"lang":"en-US","value":"The + specified resource does not exist.\nRequestId:c5dc762a-d002-00a6-1565-2a6d3a000000\nTime:2021-04-05T21:51:29.0849487Z"}}}' + headers: + cache-control: no-cache + content-type: application/json;odata=minimalmetadata;streaming=true;charset=utf-8 + date: Mon, 05 Apr 2021 21:51:28 GMT + server: Windows-Azure-Table/1.0 Microsoft-HTTPAPI/2.0 + transfer-encoding: chunked + x-content-type-options: nosniff + x-ms-version: '2019-02-02' + status: + code: 404 + message: Not Found + url: https://seankaneprim.table.core.windows.net/Tables('uttablef7b718ea') +version: 1 diff --git a/sdk/tables/azure-data-tables/tests/recordings/test_retry_async.test_retry_on_server_error_async.yaml b/sdk/tables/azure-data-tables/tests/recordings/test_retry_async.test_retry_on_server_error_async.yaml new file mode 100644 index 000000000000..96f9ff6069f3 --- /dev/null +++ b/sdk/tables/azure-data-tables/tests/recordings/test_retry_async.test_retry_on_server_error_async.yaml @@ -0,0 +1,136 @@ +interactions: +- request: + body: '{"TableName": "uttable1df148e"}' + headers: + Accept: + - application/json;odata=minimalmetadata + Content-Length: + - '31' + Content-Type: + - application/json;odata=nometadata + DataServiceVersion: + - '3.0' + Date: + - Mon, 05 Apr 2021 21:51:29 GMT + User-Agent: + - azsdk-python-data-tables/12.0.0b6 Python/3.7.4 (Windows-10-10.0.19041-SP0) + x-ms-date: + - Mon, 05 Apr 2021 21:51:29 GMT + x-ms-version: + - '2019-02-02' + method: POST + uri: https://fake_table_account.table.core.windows.net/Tables + response: + body: + string: '{"odata.metadata":"https://fake_table_account.table.core.windows.net/$metadata#Tables/@Element","TableName":"uttable1df148e"}' + headers: + cache-control: no-cache + content-type: application/json;odata=minimalmetadata;streaming=true;charset=utf-8 + date: Mon, 05 Apr 2021 21:51:28 GMT + location: https://fake_table_account.table.core.windows.net/Tables('uttable1df148e') + server: Windows-Azure-Table/1.0 Microsoft-HTTPAPI/2.0 + transfer-encoding: chunked + x-content-type-options: nosniff + x-ms-version: '2019-02-02' + status: + code: 201 + message: Created + url: https://seankaneprim.table.core.windows.net/Tables +- request: + body: '{"TableName": "uttable1df148e"}' + headers: + Accept: + - application/json;odata=minimalmetadata + Content-Length: + - '31' + Content-Type: + - application/json;odata=nometadata + DataServiceVersion: + - '3.0' + Date: + - Mon, 05 Apr 2021 21:51:29 GMT + User-Agent: + - azsdk-python-data-tables/12.0.0b6 Python/3.7.4 (Windows-10-10.0.19041-SP0) + x-ms-date: + - Mon, 05 Apr 2021 21:51:29 GMT + x-ms-version: + - '2019-02-02' + method: POST + uri: https://fake_table_account.table.core.windows.net/Tables + response: + body: + string: '{"odata.error":{"code":"TableAlreadyExists","message":{"lang":"en-US","value":"The + table specified already exists.\nRequestId:33e9accd-6002-00a3-4c65-2abfe1000000\nTime:2021-04-05T21:51:48.9118439Z"}}}' + headers: + cache-control: no-cache + content-type: application/json;odata=minimalmetadata;streaming=true;charset=utf-8 + date: Mon, 05 Apr 2021 21:51:48 GMT + server: Windows-Azure-Table/1.0 Microsoft-HTTPAPI/2.0 + transfer-encoding: chunked + x-content-type-options: nosniff + x-ms-version: '2019-02-02' + status: + code: 409 + message: Conflict + url: https://seankaneprim.table.core.windows.net/Tables +- request: + body: null + headers: + Accept: + - application/json + Date: + - Mon, 05 Apr 2021 21:51:48 GMT + User-Agent: + - azsdk-python-data-tables/12.0.0b6 Python/3.7.4 (Windows-10-10.0.19041-SP0) + x-ms-date: + - Mon, 05 Apr 2021 21:51:48 GMT + x-ms-version: + - '2019-02-02' + method: DELETE + uri: https://fake_table_account.table.core.windows.net/Tables('uttable1df148e') + response: + body: + string: '' + headers: + cache-control: no-cache + content-length: '0' + date: Mon, 05 Apr 2021 21:51:48 GMT + server: Windows-Azure-Table/1.0 Microsoft-HTTPAPI/2.0 + x-content-type-options: nosniff + x-ms-version: '2019-02-02' + status: + code: 204 + message: No Content + url: https://seankaneprim.table.core.windows.net/Tables('uttable1df148e') +- request: + body: null + headers: + Accept: + - application/json + Date: + - Mon, 05 Apr 2021 21:51:48 GMT + User-Agent: + - azsdk-python-data-tables/12.0.0b6 Python/3.7.4 (Windows-10-10.0.19041-SP0) + x-ms-date: + - Mon, 05 Apr 2021 21:51:48 GMT + x-ms-version: + - '2019-02-02' + method: DELETE + uri: https://fake_table_account.table.core.windows.net/Tables('uttable1df148e') + response: + body: + string: '{"odata.error":{"code":"ResourceNotFound","message":{"lang":"en-US","value":"The + specified resource does not exist.\nRequestId:33e9acd2-6002-00a3-4f65-2abfe1000000\nTime:2021-04-05T21:51:48.9518715Z"}}}' + headers: + cache-control: no-cache + content-type: application/json;odata=minimalmetadata;streaming=true;charset=utf-8 + date: Mon, 05 Apr 2021 21:51:48 GMT + server: Windows-Azure-Table/1.0 Microsoft-HTTPAPI/2.0 + transfer-encoding: chunked + x-content-type-options: nosniff + x-ms-version: '2019-02-02' + status: + code: 404 + message: Not Found + url: https://seankaneprim.table.core.windows.net/Tables('uttable1df148e') +version: 1 diff --git a/sdk/tables/azure-data-tables/tests/recordings/test_retry_async.test_retry_on_socket_timeout_async.yaml b/sdk/tables/azure-data-tables/tests/recordings/test_retry_async.test_retry_on_socket_timeout_async.yaml new file mode 100644 index 000000000000..f1474e254cc1 --- /dev/null +++ b/sdk/tables/azure-data-tables/tests/recordings/test_retry_async.test_retry_on_socket_timeout_async.yaml @@ -0,0 +1,62 @@ +interactions: +- request: + body: null + headers: + Accept: + - application/json + Date: + - Mon, 05 Apr 2021 21:53:16 GMT + User-Agent: + - azsdk-python-data-tables/12.0.0b6 Python/3.7.4 (Windows-10-10.0.19041-SP0) + x-ms-date: + - Mon, 05 Apr 2021 21:53:16 GMT + x-ms-version: + - '2019-02-02' + method: DELETE + uri: https://fake_table_account.table.core.windows.net/Tables('uttable2b89155d') + response: + body: + string: '' + headers: + cache-control: no-cache + content-length: '0' + date: Mon, 05 Apr 2021 21:53:16 GMT + server: Windows-Azure-Table/1.0 Microsoft-HTTPAPI/2.0 + x-content-type-options: nosniff + x-ms-version: '2019-02-02' + status: + code: 204 + message: No Content + url: https://seankaneprim.table.core.windows.net/Tables('uttable2b89155d') +- request: + body: null + headers: + Accept: + - application/json + Date: + - Mon, 05 Apr 2021 21:53:16 GMT + User-Agent: + - azsdk-python-data-tables/12.0.0b6 Python/3.7.4 (Windows-10-10.0.19041-SP0) + x-ms-date: + - Mon, 05 Apr 2021 21:53:16 GMT + x-ms-version: + - '2019-02-02' + method: DELETE + uri: https://fake_table_account.table.core.windows.net/Tables('uttable2b89155d') + response: + body: + string: '{"odata.error":{"code":"ResourceNotFound","message":{"lang":"en-US","value":"The + specified resource does not exist.\nRequestId:c5dc9c39-d002-00a6-5e66-2a6d3a000000\nTime:2021-04-05T21:53:16.8613108Z"}}}' + headers: + cache-control: no-cache + content-type: application/json;odata=minimalmetadata;streaming=true;charset=utf-8 + date: Mon, 05 Apr 2021 21:53:16 GMT + server: Windows-Azure-Table/1.0 Microsoft-HTTPAPI/2.0 + transfer-encoding: chunked + x-content-type-options: nosniff + x-ms-version: '2019-02-02' + status: + code: 404 + message: Not Found + url: https://seankaneprim.table.core.windows.net/Tables('uttable2b89155d') +version: 1 diff --git a/sdk/tables/azure-data-tables/tests/recordings/test_retry_async.test_retry_on_timeout_async.yaml b/sdk/tables/azure-data-tables/tests/recordings/test_retry_async.test_retry_on_timeout_async.yaml new file mode 100644 index 000000000000..1642a42cbaf0 --- /dev/null +++ b/sdk/tables/azure-data-tables/tests/recordings/test_retry_async.test_retry_on_timeout_async.yaml @@ -0,0 +1,136 @@ +interactions: +- request: + body: '{"TableName": "uttable9f4b1275"}' + headers: + Accept: + - application/json;odata=minimalmetadata + Content-Length: + - '32' + Content-Type: + - application/json;odata=nometadata + DataServiceVersion: + - '3.0' + Date: + - Mon, 05 Apr 2021 21:53:16 GMT + User-Agent: + - azsdk-python-data-tables/12.0.0b6 Python/3.7.4 (Windows-10-10.0.19041-SP0) + x-ms-date: + - Mon, 05 Apr 2021 21:53:16 GMT + x-ms-version: + - '2019-02-02' + method: POST + uri: https://fake_table_account.table.core.windows.net/Tables + response: + body: + string: '{"odata.metadata":"https://fake_table_account.table.core.windows.net/$metadata#Tables/@Element","TableName":"uttable9f4b1275"}' + headers: + cache-control: no-cache + content-type: application/json;odata=minimalmetadata;streaming=true;charset=utf-8 + date: Mon, 05 Apr 2021 21:53:16 GMT + location: https://fake_table_account.table.core.windows.net/Tables('uttable9f4b1275') + server: Windows-Azure-Table/1.0 Microsoft-HTTPAPI/2.0 + transfer-encoding: chunked + x-content-type-options: nosniff + x-ms-version: '2019-02-02' + status: + code: 201 + message: Created + url: https://seankaneprim.table.core.windows.net/Tables +- request: + body: '{"TableName": "uttable9f4b1275"}' + headers: + Accept: + - application/json;odata=minimalmetadata + Content-Length: + - '32' + Content-Type: + - application/json;odata=nometadata + DataServiceVersion: + - '3.0' + Date: + - Mon, 05 Apr 2021 21:53:16 GMT + User-Agent: + - azsdk-python-data-tables/12.0.0b6 Python/3.7.4 (Windows-10-10.0.19041-SP0) + x-ms-date: + - Mon, 05 Apr 2021 21:53:16 GMT + x-ms-version: + - '2019-02-02' + method: POST + uri: https://fake_table_account.table.core.windows.net/Tables + response: + body: + string: '{"odata.error":{"code":"TableAlreadyExists","message":{"lang":"en-US","value":"The + table specified already exists.\nRequestId:27a14ef8-3002-0007-6766-2ab647000000\nTime:2021-04-05T21:53:37.7044890Z"}}}' + headers: + cache-control: no-cache + content-type: application/json;odata=minimalmetadata;streaming=true;charset=utf-8 + date: Mon, 05 Apr 2021 21:53:37 GMT + server: Windows-Azure-Table/1.0 Microsoft-HTTPAPI/2.0 + transfer-encoding: chunked + x-content-type-options: nosniff + x-ms-version: '2019-02-02' + status: + code: 409 + message: Conflict + url: https://seankaneprim.table.core.windows.net/Tables +- request: + body: null + headers: + Accept: + - application/json + Date: + - Mon, 05 Apr 2021 21:53:37 GMT + User-Agent: + - azsdk-python-data-tables/12.0.0b6 Python/3.7.4 (Windows-10-10.0.19041-SP0) + x-ms-date: + - Mon, 05 Apr 2021 21:53:37 GMT + x-ms-version: + - '2019-02-02' + method: DELETE + uri: https://fake_table_account.table.core.windows.net/Tables('uttable9f4b1275') + response: + body: + string: '' + headers: + cache-control: no-cache + content-length: '0' + date: Mon, 05 Apr 2021 21:53:37 GMT + server: Windows-Azure-Table/1.0 Microsoft-HTTPAPI/2.0 + x-content-type-options: nosniff + x-ms-version: '2019-02-02' + status: + code: 204 + message: No Content + url: https://seankaneprim.table.core.windows.net/Tables('uttable9f4b1275') +- request: + body: null + headers: + Accept: + - application/json + Date: + - Mon, 05 Apr 2021 21:53:37 GMT + User-Agent: + - azsdk-python-data-tables/12.0.0b6 Python/3.7.4 (Windows-10-10.0.19041-SP0) + x-ms-date: + - Mon, 05 Apr 2021 21:53:37 GMT + x-ms-version: + - '2019-02-02' + method: DELETE + uri: https://fake_table_account.table.core.windows.net/Tables('uttable9f4b1275') + response: + body: + string: '{"odata.error":{"code":"ResourceNotFound","message":{"lang":"en-US","value":"The + specified resource does not exist.\nRequestId:27a14f00-3002-0007-6d66-2ab647000000\nTime:2021-04-05T21:53:37.7625305Z"}}}' + headers: + cache-control: no-cache + content-type: application/json;odata=minimalmetadata;streaming=true;charset=utf-8 + date: Mon, 05 Apr 2021 21:53:37 GMT + server: Windows-Azure-Table/1.0 Microsoft-HTTPAPI/2.0 + transfer-encoding: chunked + x-content-type-options: nosniff + x-ms-version: '2019-02-02' + status: + code: 404 + message: Not Found + url: https://seankaneprim.table.core.windows.net/Tables('uttable9f4b1275') +version: 1 diff --git a/sdk/tables/azure-data-tables/tests/test_retry.py b/sdk/tables/azure-data-tables/tests/test_retry.py new file mode 100644 index 000000000000..00deded55c32 --- /dev/null +++ b/sdk/tables/azure-data-tables/tests/test_retry.py @@ -0,0 +1,187 @@ +# ------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for +# license information. +# -------------------------------------------------------------------------- +import unittest +import pytest + +from devtools_testutils import AzureTestCase + + +from azure.core.exceptions import ( + HttpResponseError, + ResourceExistsError, + AzureError, + ClientAuthenticationError +) +from azure.core.pipeline.transport import( + RequestsTransport +) + +from azure.data.tables import ( + TableServiceClient, + LocationMode, + LinearRetry, + ExponentialRetry, +) + +from _shared.testcase import ( + TableTestCase, + ResponseCallback, + RetryCounter +) + +from preparers import TablesPreparer + + +class RetryRequestTransport(RequestsTransport): + """Transport to test retry""" + def __init__(self, *args, **kwargs): + super(RetryRequestTransport, self).__init__(*args, **kwargs) + self.count = 0 + + def send(self, request, **kwargs): + self.count += 1 + response = super(RetryRequestTransport, self).send(request, **kwargs) + return response + +# --Test Class ----------------------------------------------------------------- +class StorageRetryTest(AzureTestCase, TableTestCase): + + def _set_up(self, tables_storage_account_name, tables_primary_storage_account_key, url='table', default_table=True, **kwargs): + self.table_name = self.get_resource_name('uttable') + self.ts = TableServiceClient( + self.account_url(tables_storage_account_name, url), + credential=tables_primary_storage_account_key, + **kwargs + ) + self.table = self.ts.get_table_client(self.table_name) + if self.is_live and default_table: + try: + self.ts.create_table(self.table_name) + except ResourceExistsError: + pass + + self.query_tables = [] + + def _tear_down(self, **kwargs): + if self.is_live: + try: + self.ts.delete_table(self.table_name, **kwargs) + except: + pass + + try: + for table_name in self.query_tables: + try: + self.ts.delete_table(table_name, **kwargs) + except: + pass + except AttributeError: + pass + + # --Test Cases -------------------------------------------- + @TablesPreparer() + def test_retry_on_server_error(self, tables_storage_account_name, tables_primary_storage_account_key): + self._set_up(tables_storage_account_name, tables_primary_storage_account_key, default_table=False) + try: + callback = ResponseCallback(status=201, new_status=500).override_status + + new_table_name = self.get_resource_name('uttable') + # The initial create will return 201, but we overwrite it with 500 and retry. + # The retry will then get a 409 conflict. + with pytest.raises(ResourceExistsError): + self.ts.create_table(new_table_name, raw_response_hook=callback) + finally: + self.ts.delete_table(new_table_name) + self._tear_down() + + + @TablesPreparer() + def test_retry_on_timeout(self, tables_storage_account_name, tables_primary_storage_account_key): + retry = ExponentialRetry(initial_backoff=1, increment_base=2) + self._set_up(tables_storage_account_name, tables_primary_storage_account_key, retry_policy=retry, default_table=False) + + new_table_name = self.get_resource_name('uttable') + callback = ResponseCallback(status=201, new_status=408).override_status + + try: + # The initial create will return 201, but we overwrite it with 408 and retry. + # The retry will then get a 409 conflict. + with pytest.raises(ResourceExistsError): + self.ts.create_table(new_table_name, raw_response_hook=callback) + finally: + self.ts.delete_table(new_table_name) + self._tear_down() + + + @TablesPreparer() + def test_retry_callback_and_retry_context(self, tables_storage_account_name, tables_primary_storage_account_key): + retry = LinearRetry(backoff=1) + self._set_up(tables_storage_account_name, tables_primary_storage_account_key, retry_policy=retry, default_table=False) + + new_table_name = self.get_resource_name('uttable') + callback = ResponseCallback(status=201, new_status=408).override_status + + def assert_exception_is_present_on_retry_context(**kwargs): + self.assertIsNotNone(kwargs.get('response')) + self.assertEqual(kwargs['response'].status_code, 408) + try: + # The initial create will return 201, but we overwrite it with 408 and retry. + # The retry will then get a 409 conflict. + with pytest.raises(ResourceExistsError): + self.ts.create_table(new_table_name, raw_response_hook=callback, retry_hook=assert_exception_is_present_on_retry_context) + finally: + self.ts.delete_table(new_table_name) + self._tear_down() + + @pytest.mark.live_test_only + @TablesPreparer() + def test_retry_on_socket_timeout(self, tables_storage_account_name, tables_primary_storage_account_key): + retry = LinearRetry(backoff=1) + retry_transport = RetryRequestTransport(connection_timeout=11, read_timeout=0.000000000001) + self._set_up( + tables_storage_account_name, + tables_primary_storage_account_key, + retry_policy=retry, + transport=retry_transport, + default_table=False) + + new_table_name = self.get_resource_name('uttable') + try: + with pytest.raises(AzureError) as error: + self.ts.create_table(new_table_name) + + # 3 retries + 1 original == 4 + assert retry_transport.count == 4 + # This call should succeed on the server side, but fail on the client side due to socket timeout + self.assertTrue('read timeout' in str(error.value), 'Expected socket timeout but got different exception.') + + finally: + # we must make the timeout normal again to let the delete operation succeed + self.ts.delete_table(new_table_name, connection_timeout=(11, 11)) + self._tear_down(connection_timeout=(11, 11)) + + + # Waiting on fix to client pipeline + # @TablesPreparer() + # def test_no_retry(self, tables_storage_account_name, tables_primary_storage_account_key): + # self._set_up(tables_storage_account_name, tables_primary_storage_account_key, retry_total=0, default_table=False) + + # new_table_name = self.get_resource_name('uttable') + + # # Force the create call to 'timeout' with a 408 + # callback = ResponseCallback(status=201, new_status=408).override_status + + # try: + # with pytest.raises(HttpResponseError) as error: + # self.ts.create_table(new_table_name, raw_response_hook=callback) + # self.assertEqual(error.value.response.status_code, 408) + # self.assertEqual(error.value.reason, 'Created') + + # finally: + # self.ts.delete_table(new_table_name) + # self._tear_down() +# ------------------------------------------------------------------------------ + diff --git a/sdk/tables/azure-data-tables/tests/test_retry_async.py b/sdk/tables/azure-data-tables/tests/test_retry_async.py new file mode 100644 index 000000000000..9d71522723fc --- /dev/null +++ b/sdk/tables/azure-data-tables/tests/test_retry_async.py @@ -0,0 +1,187 @@ +# ------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for +# license information. +# -------------------------------------------------------------------------- +import unittest +import pytest + +from devtools_testutils import AzureTestCase + + +from azure.core.exceptions import ( + HttpResponseError, + ResourceExistsError, + AzureError, + ClientAuthenticationError +) +from azure.core.pipeline.transport import( + AioHttpTransport +) + +from azure.data.tables.aio import TableServiceClient +from azure.data.tables.aio._policies_async import LinearRetry, ExponentialRetry +from azure.data.tables import LocationMode + +from _shared.asynctestcase import AsyncTableTestCase +from _shared.testcase import ( + ResponseCallback, + RetryCounter +) + +from preparers import TablesPreparer + + +class RetryAioHttpTransport(AioHttpTransport): + """Transport to test retry""" + def __init__(self, *args, **kwargs): + super(RetryAioHttpTransport, self).__init__(*args, **kwargs) + self.count = 0 + + async def send(self, request, **kwargs): + self.count += 1 + response = await super(RetryAioHttpTransport, self).send(request, **kwargs) + return response + + +# --Test Class ----------------------------------------------------------------- +class StorageRetryTest(AzureTestCase, AsyncTableTestCase): + + async def _set_up(self, tables_storage_account_name, tables_primary_storage_account_key, url='table', default_table=True, **kwargs): + self.table_name = self.get_resource_name('uttable') + self.ts = TableServiceClient( + self.account_url(tables_storage_account_name, url), + credential=tables_primary_storage_account_key, + **kwargs + ) + self.table = self.ts.get_table_client(self.table_name) + if self.is_live and default_table: + try: + await self.ts.create_table(self.table_name) + except ResourceExistsError: + pass + + self.query_tables = [] + + async def _tear_down(self, **kwargs): + if self.is_live: + try: + await self.ts.delete_table(self.table_name, **kwargs) + except: + pass + + try: + for table_name in self.query_tables: + try: + await self.ts.delete_table(table_name, **kwargs) + except: + pass + except AttributeError: + pass + + # --Test Cases -------------------------------------------- + @TablesPreparer() + async def test_retry_on_server_error_async(self, tables_storage_account_name, tables_primary_storage_account_key): + await self._set_up(tables_storage_account_name, tables_primary_storage_account_key, default_table=False) + try: + callback = ResponseCallback(status=201, new_status=500).override_status + + new_table_name = self.get_resource_name('uttable') + # The initial create will return 201, but we overwrite it with 500 and retry. + # The retry will then get a 409 conflict. + with pytest.raises(ResourceExistsError): + await self.ts.create_table(new_table_name, raw_response_hook=callback) + finally: + await self.ts.delete_table(new_table_name) + await self._tear_down() + + + @TablesPreparer() + async def test_retry_on_timeout_async(self, tables_storage_account_name, tables_primary_storage_account_key): + retry = ExponentialRetry(initial_backoff=1, increment_base=2) + await self._set_up(tables_storage_account_name, tables_primary_storage_account_key, retry_policy=retry, default_table=False) + + new_table_name = self.get_resource_name('uttable') + callback = ResponseCallback(status=201, new_status=408).override_status + + try: + # The initial create will return 201, but we overwrite it with 408 and retry. + # The retry will then get a 409 conflict. + with pytest.raises(ResourceExistsError): + await self.ts.create_table(new_table_name, raw_response_hook=callback) + finally: + await self.ts.delete_table(new_table_name) + await self._tear_down() + + + @TablesPreparer() + async def test_retry_callback_and_retry_context_async(self, tables_storage_account_name, tables_primary_storage_account_key): + retry = LinearRetry(backoff=1) + await self._set_up(tables_storage_account_name, tables_primary_storage_account_key, retry_policy=retry, default_table=False) + + new_table_name = self.get_resource_name('uttable') + callback = ResponseCallback(status=201, new_status=408).override_status + + def assert_exception_is_present_on_retry_context(**kwargs): + self.assertIsNotNone(kwargs.get('response')) + self.assertEqual(kwargs['response'].status_code, 408) + try: + # The initial create will return 201, but we overwrite it with 408 and retry. + # The retry will then get a 409 conflict. + with pytest.raises(ResourceExistsError): + await self.ts.create_table(new_table_name, raw_response_hook=callback, retry_hook=assert_exception_is_present_on_retry_context) + finally: + await self.ts.delete_table(new_table_name) + await self._tear_down() + + @pytest.mark.live_test_only + @TablesPreparer() + async def test_retry_on_socket_timeout_async(self, tables_storage_account_name, tables_primary_storage_account_key): + retry = LinearRetry(backoff=1) + retry_transport = RetryAioHttpTransport(connection_timeout=11, read_timeout=0.000000000001) + await self._set_up( + tables_storage_account_name, + tables_primary_storage_account_key, + retry_policy=retry, + transport=retry_transport, + default_table=False) + + new_table_name = self.get_resource_name('uttable') + try: + with pytest.raises(AzureError) as error: + await self.ts.create_table(new_table_name) + + # 3 retries + 1 original == 4 + assert retry_transport.count == 4 + # This call should succeed on the server side, but fail on the client side due to socket timeout + self.assertTrue('Timeout on reading' in str(error.value), 'Expected socket timeout but got different exception.') + + finally: + # TODO: Why can I not just reset the connection timeout??? + await self._set_up(tables_storage_account_name, tables_primary_storage_account_key, default_table=False) + # we must make the timeout normal again to let the delete operation succeed + await self.ts.delete_table(new_table_name) + await self._tear_down() + + + # Waiting on fix to client pipeline + # @TablesPreparer() + # async def test_no_retry_async(self, tables_storage_account_name, tables_primary_storage_account_key): + # await self._set_up(tables_storage_account_name, tables_primary_storage_account_key, retry_total=0, default_table=False) + + # new_table_name = self.get_resource_name('uttable') + + # # Force the create call to 'timeout' with a 408 + # callback = ResponseCallback(status=201, new_status=408).override_status + + # try: + # with with pytest.raises(HttpResponseError) as error: + # await self.ts.create_table(new_table_name, raw_response_hook=callback) + # self.assertEqual(error.value.response.status_code, 408) + # self.assertEqual(error.value.reason, 'Created') + + # finally: + # await self.ts.delete_table(new_table_name) + # await self._tear_down() +# ------------------------------------------------------------------------------ +