async def _test_invalid_account_key_async(self): # Arrange container_name = self.get_resource_name() retry = ExponentialRetry(initial_backoff=1, increment_base=3, retry_total=3) service = self._create_storage_service( BlobServiceClient, self.settings, retry_policy=retry, transport=AiohttpTestTransport()) service.credential.account_name = "dummy_account_name" service.credential.account_key = "dummy_account_key" # Shorten retries and add counter retry_counter = RetryCounter() retry_callback = retry_counter.simple_count # Act with self.assertRaises(ClientAuthenticationError): await service.create_container(container_name, retry_callback=retry_callback) # Assert # No retry should be performed since the signing error is fatal self.assertEqual(retry_counter.count, 0)
async def _test_retry_to_secondary_with_get_async(self): # Arrange container_name = self.get_resource_name() retry = ExponentialRetry(retry_to_secondary=True, initial_backoff=1, increment_base=2) service = self._create_storage_service( BlobServiceClient, self.settings, retry_policy=retry, transport=AiohttpTestTransport()) # Act try: container = await service.create_container(container_name) response_callback = ResponseCallback( status=200, new_status=408).override_first_status # Assert # Confirm that the get request gets retried to secondary def retry_callback(retry_count=None, location_mode=None, **kwargs): # Only check this every other time, sometimes the secondary location fails due to delay if retry_count % 2 == 0: self.assertEqual(LocationMode.SECONDARY, location_mode) await container.get_container_properties( raw_response_hook=response_callback, retry_hook=retry_callback) finally: await service.delete_container(container_name)
async def _test_secondary_location_mode_async(self): # Arrange container_name = self.get_resource_name() retry = ExponentialRetry(initial_backoff=1, increment_base=2) service = self._create_storage_service( BlobServiceClient, self.settings, retry_policy=retry, transport=AiohttpTestTransport()) # Act try: container = await service.create_container(container_name) container.location_mode = LocationMode.SECONDARY # Override the response from secondary if it's 404 as that simply means # the container hasn't replicated. We're just testing we try secondary, # so that's fine. response_callback = ResponseCallback( status=404, new_status=200).override_first_status # Assert def request_callback(request): self.assertNotEqual( -1, request.http_request.url.find('-secondary')) request_callback = request_callback await container.get_container_properties( raw_request_hook=request_callback, raw_response_hook=response_callback) finally: # Delete will go to primary, so disable the request validation await service.delete_container(container_name)
async def _test_retry_to_secondary_with_put_async(self): # Arrange container_name = self.get_resource_name() retry = ExponentialRetry(retry_to_secondary=True, initial_backoff=1, increment_base=2) service = self._create_storage_service( BlobServiceClient, self.settings, retry_policy=retry, transport=AiohttpTestTransport()) # Act try: # Fail the first create attempt response_callback = ResponseCallback( status=201, new_status=408).override_first_status # Assert # Confirm that the create request does *not* get retried to secondary # This should actually throw InvalidPermissions if sent to secondary, # but validate the location_mode anyways. def retry_callback(location_mode=None, **kwargs): self.assertEqual(LocationMode.PRIMARY, location_mode) with self.assertRaises(ResourceExistsError): await service.create_container( container_name, raw_response_hook=response_callback, retry_hook=retry_callback) finally: await service.delete_container(container_name)
async def _test_exponential_retry_async(self): # Arrange container_name = self.get_resource_name() retry = ExponentialRetry(initial_backoff=1, increment_base=3, retry_total=3) service = self._create_storage_service( BlobServiceClient, self.settings, retry_policy=retry, transport=AiohttpTestTransport()) try: container = await service.create_container(container_name) # Force the create call to 'timeout' with a 408 callback = ResponseCallback(status=200, new_status=408) # Act with self.assertRaises(HttpResponseError): await container.get_container_properties( raw_response_hook=callback.override_status) # Assert the response was called the right number of times (1 initial request + 3 retries) self.assertEqual(callback.count, 1 + 3) finally: # Clean up await service.delete_container(container_name)
async def _test_retry_with_deserialization_async(self): # Arrange container_name = self.get_resource_name(prefix='retry') retry = ExponentialRetry(initial_backoff=1, increment_base=2) service = self._create_storage_service( BlobServiceClient, self.settings, retry_policy=retry, transport=AiohttpTestTransport()) try: created = await service.create_container(container_name) # Act callback = ResponseCallback(status=200, new_status=408).override_first_status containers = service.list_containers(name_starts_with='retry', raw_response_hook=callback) # Assert listed = [] async for c in containers: listed.append(c) self.assertTrue(len(listed) >= 1) finally: await service.delete_container(container_name)
def test_exponential_retry_interval_async(self): # Arrange retry_policy = ExponentialRetry(initial_backoff=1, increment_base=3, random_jitter_range=3) context_stub = {} for i in range(10): # Act context_stub['count'] = 0 backoff = retry_policy.get_backoff_time(context_stub) # Assert backoff interval is within +/- 3 of 1 self.assertTrue(0 <= backoff <= 4) # Act context_stub['count'] = 1 backoff = retry_policy.get_backoff_time(context_stub) # Assert backoff interval is within +/- 3 of 4(1+3^1) self.assertTrue(1 <= backoff <= 7) # Act context_stub['count'] = 2 backoff = retry_policy.get_backoff_time(context_stub) # Assert backoff interval is within +/- 3 of 10(1+3^2) self.assertTrue(7 <= backoff <= 13) # Act context_stub['count'] = 3 backoff = retry_policy.get_backoff_time(context_stub) # Assert backoff interval is within +/- 3 of 28(1+3^3) self.assertTrue(25 <= backoff <= 31)
def setUp(self): super(StorageBlobRetryTestAsync, self).setUp() url = self._get_account_url() credential = self._get_shared_key_credential() retry = ExponentialRetry(initial_backoff=1, increment_base=2, retry_total=3) self.bs = BlobServiceClient(url, credential=credential, retry_policy=retry) self.container_name = self.get_resource_name('utcontainer')
async def test_retry_on_timeout_async(self, resource_group, location, storage_account, storage_account_key): # Arrange container_name = self.get_resource_name('utcontainer') retry = ExponentialRetry(initial_backoff=1, increment_base=2) service = self._create_storage_service( BlobServiceClient, storage_account, storage_account_key, retry_policy=retry, transport=AiohttpTestTransport()) callback = ResponseCallback(status=201, new_status=408).override_status # Act try: # The initial create will return 201, but we overwrite it and retry. # The retry will then get a 409 and return false. with self.assertRaises(ResourceExistsError): await service.create_container(container_name, raw_response_hook=callback) finally: await service.delete_container(container_name)
async def test_invalid_retry_async(self, resource_group, location, storage_account, storage_account_key): # Arrange container_name = self.get_resource_name('utcontainer') retry = ExponentialRetry(initial_backoff=1, increment_base=2) service = self._create_storage_service( BlobServiceClient, storage_account, storage_account_key, retry_policy=retry, transport=AiohttpTestTransport()) # Force the create call to fail by pretending it's a teapot callback = ResponseCallback(status=201, new_status=418).override_status # Act try: with self.assertRaises(HttpResponseError) as error: await service.create_container(container_name, raw_response_hook=callback) self.assertEqual(error.exception.response.status_code, 418) self.assertEqual(error.exception.reason, 'Created') finally: await service.delete_container(container_name)
async def _test_location_lock_async(self): # Arrange retry = ExponentialRetry(retry_to_secondary=True, initial_backoff=1, increment_base=2) service = self._create_storage_service( BlobServiceClient, self.settings, retry_policy=retry, transport=AiohttpTestTransport()) # Act # Fail the first request and set the retry policy to retry to secondary response_callback = ResponseCallback( status=200, new_status=408).override_first_status #context = _OperationContext(location_lock=True) # Assert # Confirm that the first request gets retried to secondary # The given test account must be GRS def retry_callback(retry_count=None, location_mode=None, **kwargs): self.assertEqual(LocationMode.SECONDARY, location_mode) # Confirm that the second list request done with the same context sticks # to the final location of the first list request (aka secondary) despite # the client normally trying primary first requests = [] def request_callback(request): if not requests: requests.append(request) else: self.assertNotEqual( -1, request.http_request.url.find('-secondary')) containers = service.list_containers(results_per_page=1, retry_hook=retry_callback) await containers.__anext__() await containers.__anext__()
def setUp(self): self.retry = ExponentialRetry(initial_backoff=1, increment_base=2, retry_total=3)
async def test_retry_secondary_async(self, resource_group, location, storage_account, storage_account_key): """Secondary location test. This test is special, since in pratical term, we don't have time to wait for the georeplication to be done (can take a loooooong time). So for the purpose of this test, we fake a 408 on the primary request, and then we check we do a 408. AND DONE. It's not really perfect, since we didn't tested it would work on a real geo-location. Might be changed to live only as loooooong test with a polling on the current geo-replication status. """ # Arrange # Fail the first request and set the retry policy to retry to secondary # The given test account must be GRS class MockTransport(AioHttpTransport): CALL_NUMBER = 1 ENABLE = False async def send(self, request, **kwargs): if MockTransport.ENABLE: if MockTransport.CALL_NUMBER == 2: if request.method != 'PUT': assert '-secondary' in request.url # Here's our hack # Replace with primary so the test works even # if secondary is not ready request.url = request.url.replace('-secondary', '') response = await super(MockTransport, self).send(request, **kwargs) if MockTransport.ENABLE: assert response.status_code in [200, 201, 409] if MockTransport.CALL_NUMBER == 1: response.status_code = 408 elif MockTransport.CALL_NUMBER == 2: if response.status_code == 409: # We can't really retry on PUT response.status_code = 201 else: pytest.fail( "This test is not supposed to do more calls") MockTransport.CALL_NUMBER += 1 return response retry = ExponentialRetry(retry_to_secondary=True, initial_backoff=1, increment_base=2) service = self._create_storage_service(BlobServiceClient, storage_account, storage_account_key, retry_policy=retry, transport=MockTransport()) # Act MockTransport.ENABLE = True # Assert # Try put def put_retry_callback(retry_count=None, location_mode=None, **kwargs): # This call should be called once, with the decision to try secondary put_retry_callback.called = True if MockTransport.CALL_NUMBER == 1: self.assertEqual(LocationMode.PRIMARY, location_mode) elif MockTransport.CALL_NUMBER == 2: self.assertEqual(LocationMode.PRIMARY, location_mode) else: pytest.fail( "This test is not supposed to retry more than once") put_retry_callback.called = False container = service.get_container_client('containername') created = await container.create_container( retry_hook=put_retry_callback) assert put_retry_callback.called def retry_callback(retry_count=None, location_mode=None, **kwargs): # This call should be called once, with the decision to try secondary retry_callback.called = True if MockTransport.CALL_NUMBER == 1: self.assertEqual(LocationMode.SECONDARY, location_mode) elif MockTransport.CALL_NUMBER == 2: self.assertEqual(LocationMode.SECONDARY, location_mode) else: pytest.fail( "This test is not supposed to retry more than once") retry_callback.called = False # Try list MockTransport.CALL_NUMBER = 1 retry_callback.called = False containers = service.list_containers(results_per_page=1, retry_hook=retry_callback) await containers.__anext__() assert retry_callback.called # Try get MockTransport.CALL_NUMBER = 1 retry_callback.called = False await container.get_container_properties(retry_hook=retry_callback) assert retry_callback.called