Skip to content

Commit

Permalink
IonQ: Handle 409 errors that are injected by Cloudflare (#5292)
Browse files Browse the repository at this point in the history
* IonQ: Handle 409 errors that are injected by Cloudflare

comment

Review comment

Some tests!

* Conflict

* Mocks

* fmt

---------

Co-authored-by: Coleman Collins <coleman@colemancollins.com>
  • Loading branch information
Cynocracy and ColemanCollins committed Feb 22, 2023
1 parent 1ad63aa commit 08fee59
Show file tree
Hide file tree
Showing 2 changed files with 33 additions and 7 deletions.
17 changes: 12 additions & 5 deletions cirq-ionq/cirq_ionq/ionq_client.py
Expand Up @@ -25,17 +25,24 @@
import cirq_ionq
from cirq_ionq import ionq_exceptions

# https://support.cloudflare.com/hc/en-us/articles/115003014512-4xx-Client-Error
# "Cloudflare will generate and serve a 409 response for a Error 1001: DNS Resolution Error."
# We may want to condition on the body as well, to allow for some GET requests to return 409 in
# the future.
RETRIABLE_FOR_GETS = {requests.codes.conflict}
# Retriable regardless of the source
# Handle 52x responses from cloudflare.
# See https://support.cloudflare.com/hc/en-us/articles/115003011431/
RETRIABLE_STATUS_CODES = {
requests.codes.internal_server_error,
requests.codes.bad_gateway,
requests.codes.service_unavailable,
*list(range(520, 530)),
}


def _is_retriable(code):
# Handle 52x responses from cloudflare.
# See https://support.cloudflare.com/hc/en-us/articles/115003011431/
return code in RETRIABLE_STATUS_CODES or (code >= 520 and code <= 530)
def _is_retriable(code, method):
return code in RETRIABLE_STATUS_CODES or (method == "GET" and code in RETRIABLE_FOR_GETS)


class _IonQClient:
Expand Down Expand Up @@ -304,7 +311,7 @@ def _make_request(
raise ionq_exceptions.IonQNotFoundException(
'IonQ could not find requested resource.'
)
if not _is_retriable(response.status_code):
if not _is_retriable(response.status_code, response.request.method):
error = {}
try:
error = response.json()
Expand Down
23 changes: 21 additions & 2 deletions cirq-ionq/cirq_ionq/ionq_client_test.py
Expand Up @@ -181,12 +181,12 @@ def test_ionq_client_create_job_not_found(mock_post):
@mock.patch('requests.post')
def test_ionq_client_create_job_not_retriable(mock_post):
mock_post.return_value.ok = False
mock_post.return_value.status_code = requests.codes.not_implemented
mock_post.return_value.status_code = requests.codes.conflict

client = ionq.ionq_client._IonQClient(
remote_host='http://example.com', api_key='to_my_heart', default_target='simulator'
)
with pytest.raises(ionq.IonQException, match='Status: 501'):
with pytest.raises(ionq.IonQException, match='Status: 409'):
_ = client.create_job(
serialized_program=ionq.SerializedProgram(body={'job': 'mine'}, metadata={})
)
Expand Down Expand Up @@ -246,6 +246,25 @@ def test_ionq_client_create_job_timeout(mock_post):
)


@mock.patch('requests.get')
def test_ionq_client_get_job_retry_409(mock_get):
response1 = mock.MagicMock()
response2 = mock.MagicMock()
mock_get.side_effect = [response1, response2]
response1.ok = False
response1.status_code = requests.codes.conflict
response1.request.method = "GET"
response2.ok = True
response2.json.return_value = {'foo': 'bar'}

client = ionq.ionq_client._IonQClient(remote_host='http://example.com', api_key='to_my_heart')
response = client.get_job(job_id='job_id')
assert response == {'foo': 'bar'}

expected_headers = {'Authorization': 'apiKey to_my_heart', 'Content-Type': 'application/json'}
mock_get.assert_called_with('http://example.com/v0.1/jobs/job_id', headers=expected_headers)


@mock.patch('requests.get')
def test_ionq_client_get_job(mock_get):
mock_get.return_value.ok = True
Expand Down

0 comments on commit 08fee59

Please sign in to comment.