Skip to content

Commit 08fee59

Browse files
IonQ: Handle 409 errors that are injected by Cloudflare (#5292)
* IonQ: Handle 409 errors that are injected by Cloudflare comment Review comment Some tests! * Conflict * Mocks * fmt --------- Co-authored-by: Coleman Collins <[email protected]>
1 parent 1ad63aa commit 08fee59

File tree

2 files changed

+33
-7
lines changed

2 files changed

+33
-7
lines changed

Diff for: cirq-ionq/cirq_ionq/ionq_client.py

+12-5
Original file line numberDiff line numberDiff line change
@@ -25,17 +25,24 @@
2525
import cirq_ionq
2626
from cirq_ionq import ionq_exceptions
2727

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

3443

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

4047

4148
class _IonQClient:
@@ -304,7 +311,7 @@ def _make_request(
304311
raise ionq_exceptions.IonQNotFoundException(
305312
'IonQ could not find requested resource.'
306313
)
307-
if not _is_retriable(response.status_code):
314+
if not _is_retriable(response.status_code, response.request.method):
308315
error = {}
309316
try:
310317
error = response.json()

Diff for: cirq-ionq/cirq_ionq/ionq_client_test.py

+21-2
Original file line numberDiff line numberDiff line change
@@ -181,12 +181,12 @@ def test_ionq_client_create_job_not_found(mock_post):
181181
@mock.patch('requests.post')
182182
def test_ionq_client_create_job_not_retriable(mock_post):
183183
mock_post.return_value.ok = False
184-
mock_post.return_value.status_code = requests.codes.not_implemented
184+
mock_post.return_value.status_code = requests.codes.conflict
185185

186186
client = ionq.ionq_client._IonQClient(
187187
remote_host='http://example.com', api_key='to_my_heart', default_target='simulator'
188188
)
189-
with pytest.raises(ionq.IonQException, match='Status: 501'):
189+
with pytest.raises(ionq.IonQException, match='Status: 409'):
190190
_ = client.create_job(
191191
serialized_program=ionq.SerializedProgram(body={'job': 'mine'}, metadata={})
192192
)
@@ -246,6 +246,25 @@ def test_ionq_client_create_job_timeout(mock_post):
246246
)
247247

248248

249+
@mock.patch('requests.get')
250+
def test_ionq_client_get_job_retry_409(mock_get):
251+
response1 = mock.MagicMock()
252+
response2 = mock.MagicMock()
253+
mock_get.side_effect = [response1, response2]
254+
response1.ok = False
255+
response1.status_code = requests.codes.conflict
256+
response1.request.method = "GET"
257+
response2.ok = True
258+
response2.json.return_value = {'foo': 'bar'}
259+
260+
client = ionq.ionq_client._IonQClient(remote_host='http://example.com', api_key='to_my_heart')
261+
response = client.get_job(job_id='job_id')
262+
assert response == {'foo': 'bar'}
263+
264+
expected_headers = {'Authorization': 'apiKey to_my_heart', 'Content-Type': 'application/json'}
265+
mock_get.assert_called_with('http://example.com/v0.1/jobs/job_id', headers=expected_headers)
266+
267+
249268
@mock.patch('requests.get')
250269
def test_ionq_client_get_job(mock_get):
251270
mock_get.return_value.ok = True

0 commit comments

Comments
 (0)