Skip to content

Commit 7dd9c57

Browse files
committed
fix(agent): handle None response from retry exhaustion to prevent crashes
Fix AttributeError when session.post() returns None after retry exhaustion. When the server returns repeated 503 errors, the requests retry mechanism can return None instead of a Response object, causing agents to crash when accessing status_code attribute. Fixed methods: - post_status_update() - post_result() - get_result() - repost_job() - post_artifacts() Resolves agents showing EXITED status in supervisorctl. Fixes #438 (Fixes #CERTTF-474 Internally) Fix AttributeError when session.post() returns None after retry exhaustion. When the server returns repeated 503 errors, the requests retry mechanism can return None instead of a Response object, causing agents to crash when accessing status_code attribute. Fixed methods: - post_status_update() - post_result() - get_result() - save_artifacts() Resolves agents showing EXITED status in supervisorctl. Fixes #438 (Fixes #CERTTF-474 Internally)
1 parent a53aa32 commit 7dd9c57

2 files changed

Lines changed: 94 additions & 19 deletions

File tree

agent/src/testflinger_agent/client.py

Lines changed: 47 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -182,12 +182,20 @@ def post_result(self, job_id, data):
182182
logger.error(exc)
183183
raise TFServerError("other exception") from exc
184184
if not job_request:
185-
logger.error(
186-
"Unable to post results to: %s (error: %d)",
187-
result_uri,
188-
job_request.status_code,
189-
)
190-
raise TFServerError(job_request.status_code)
185+
if job_request is None:
186+
logger.error(
187+
"Unable to post results to: %s (error: %s)",
188+
result_uri,
189+
"No response received",
190+
)
191+
raise TFServerError("No response received")
192+
else:
193+
logger.error(
194+
"Unable to post results to: %s (error: %d)",
195+
result_uri,
196+
job_request.status_code,
197+
)
198+
raise TFServerError(job_request.status_code)
191199

192200
def get_result(self, job_id):
193201
"""Get current results data to the testflinger server for this job.
@@ -206,11 +214,18 @@ def get_result(self, job_id):
206214
logger.error(exc)
207215
return {}
208216
if not job_request:
209-
logger.error(
210-
"Unable to get results from: %s (error: %d)",
211-
result_uri,
212-
job_request.status_code,
213-
)
217+
if job_request is None:
218+
logger.error(
219+
"Unable to get results from: %s (error: %s)",
220+
result_uri,
221+
"No response received",
222+
)
223+
else:
224+
logger.error(
225+
"Unable to get results from: %s (error: %d)",
226+
result_uri,
227+
job_request.status_code,
228+
)
214229
return {}
215230
if job_request.content:
216231
return job_request.json()
@@ -299,12 +314,20 @@ def save_artifacts(self, rundir, job_id):
299314
artifact_uri, files=file_upload, timeout=600
300315
)
301316
if not artifact_request:
302-
logger.error(
303-
"Unable to post results to: %s (error: %d)",
304-
artifact_uri,
305-
artifact_request.status_code,
306-
)
307-
raise TFServerError(artifact_request.status_code)
317+
if artifact_request is None:
318+
logger.error(
319+
"Unable to post results to: %s (error: %s)",
320+
artifact_uri,
321+
"No response received",
322+
)
323+
raise TFServerError("No response received")
324+
else:
325+
logger.error(
326+
"Unable to post results to: %s (error: %d)",
327+
artifact_uri,
328+
artifact_request.status_code,
329+
)
330+
raise TFServerError(artifact_request.status_code)
308331
else:
309332
shutil.rmtree(artifacts_dir)
310333

@@ -490,10 +513,15 @@ def post_status_update(
490513
)
491514
# Response code is greater than 399
492515
if not job_request:
516+
error_msg = (
517+
"No response received"
518+
if job_request is None
519+
else f"HTTP {job_request.status_code}"
520+
)
493521
logger.error(
494-
"Unable to post status updates to: %s (error: %d)",
522+
"Unable to post status updates to: %s (error: %s)",
495523
status_update_uri,
496-
job_request.status_code,
524+
error_msg,
497525
)
498526
except requests.exceptions.RequestException as exc:
499527
logger.error(

agent/tests/test_client.py

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -291,3 +291,50 @@ def test_get_agent_data(self, client, requests_mock):
291291

292292
data = client.get_agent_data("test_agent")
293293
assert data == agent_data
294+
295+
def test_post_status_update_none_response(self, client, caplog):
296+
"""
297+
Test that post_status_update handles None response gracefully.
298+
This simulates the case where retry exhaustion returns None.
299+
"""
300+
job_id = str(uuid.uuid1())
301+
302+
# Mock the session.post to return None (simulating retry exhaustion)
303+
with patch.object(client.session, "post", return_value=None):
304+
client.post_status_update("myjobqueue", "http://foo", [], job_id)
305+
assert "No response received" in caplog.text
306+
307+
def test_post_result_none_response(self, client, caplog):
308+
"""Test that post_result handles None response gracefully."""
309+
job_id = str(uuid.uuid1())
310+
311+
with patch.object(client.session, "post", return_value=None):
312+
with pytest.raises(TFServerError, match="No response received"):
313+
client.post_result(job_id, {"test": "data"})
314+
assert "No response received" in caplog.text
315+
316+
def test_get_result_none_response(self, client, caplog):
317+
"""Test that get_result handles None response gracefully."""
318+
job_id = str(uuid.uuid1())
319+
320+
with patch.object(client.session, "get", return_value=None):
321+
result = client.get_result(job_id)
322+
assert result == {}
323+
assert "No response received" in caplog.text
324+
325+
def test_save_artifacts_error_response(
326+
self, client, requests_mock, tmp_path, caplog
327+
):
328+
"""Test that save_artifacts handles HTTP error response correctly."""
329+
job_id = str(uuid.uuid1())
330+
331+
# Create artifacts directory with a file
332+
artifacts_dir = tmp_path / "artifacts"
333+
artifacts_dir.mkdir()
334+
(artifacts_dir / "test.txt").write_text("test content")
335+
336+
artifact_uri = f"http://127.0.0.1:8000/v1/result/{job_id}/artifact"
337+
requests_mock.post(artifact_uri, status_code=500)
338+
with pytest.raises(TFServerError):
339+
client.save_artifacts(tmp_path, job_id)
340+
assert "Unable to post results" in caplog.text

0 commit comments

Comments
 (0)