Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 11 additions & 1 deletion packit_service/worker/helpers/testing_farm_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
from ogr.utils import RequestResponse
from packit.constants import HTTP_REQUEST_TIMEOUT
from packit.exceptions import PackitException
from urllib3.util.retry import Retry

from packit_service.config import ServiceConfig
from packit_service.constants import (
Expand All @@ -33,7 +34,16 @@ def __init__(self, api_url: str, token: str, use_internal_tf: bool = False) -> N
self._token = token

self.session = requests.session()
self.session.mount("https://", requests.adapters.HTTPAdapter(max_retries=5))
retry_strategy = Retry(
total=5,
backoff_factor=1,
status_forcelist=[500, 502, 503, 504],
allowed_methods=["GET", "POST", "DELETE"],

@betulependule betulependule Aug 14, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am unsure how safe it would be to just blindly retry POST requests. I would remove it as it seems to me that all POST requests are already covered by existing retry mechanisms (unless I am mistaken).

I can see that TestingFarmJobHelper.prepare_and_send_tf_request() makes a POST request and the method's dosctring states:

Prepare the payload that will be sent to Testing Farm, submit it to
TF API and handle the response (report whether the request was sent
successfully, store the new TF run in DB or retry if needed).

It uses the def _retry_on_submit_failure() method for this. Same goes for DownstreamTestingFarmJobHelper.prepare_and_send_tf_request().

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think having retries at multiple levels hurts anything, I think it could even save some resources. But perhaps @thrix can confirm if retrying POST requests on 5xx errors is safe.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I asked in chat and it should safe with the backoff timer that is used, so I'm fine with merging the PR as is.

)
self.session.mount(
"https://",
requests.adapters.HTTPAdapter(max_retries=retry_strategy),
)
self.session.headers.update({"Authorization": f"Bearer {self._token}"})

@property
Expand Down
43 changes: 43 additions & 0 deletions tests/unit/test_testing_farm.py
Original file line number Diff line number Diff line change
Expand Up @@ -2480,3 +2480,46 @@ def test_empty_development_list(self):
{"fedora-development": []},
)
assert TFJobHelper.is_freshly_branched_fedora("fedora-42") is False


class TestTestingFarmClientRetry:
"""Tests for the retry mechanism configured on the TF API client."""

def test_retry_strategy_is_configured(self):
"""Verify that the session retry strategy retries on 5xx status codes."""
from urllib3.util.retry import Retry

service_config = ServiceConfig.get_service_config()
client = TFClient(
api_url=service_config.testing_farm_api_url,
token=service_config.testing_farm_secret,
)
adapter = client.session.get_adapter("https://example.com")
retry = adapter.max_retries

assert isinstance(retry, Retry)
assert retry.total == 5
assert retry.backoff_factor == 1
assert 500 in retry.status_forcelist
assert 502 in retry.status_forcelist
assert 503 in retry.status_forcelist
assert 504 in retry.status_forcelist

def test_retry_allows_post_and_delete(self):
"""Verify that POST and DELETE methods are retried (needed for TF
request submission and cancellation)."""
from urllib3.util.retry import Retry

service_config = ServiceConfig.get_service_config()
client = TFClient(
api_url=service_config.testing_farm_api_url,
token=service_config.testing_farm_secret,
)
adapter = client.session.get_adapter("https://example.com")
retry = adapter.max_retries

assert isinstance(retry, Retry)
allowed = retry.allowed_methods
assert "GET" in allowed
assert "POST" in allowed
assert "DELETE" in allowed
Loading