summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorClayton Walker <cwalker@sofi.org>2022-03-02 11:34:05 -0700
committerNejc Habjan <nejc.habjan@siemens.com>2022-04-04 23:30:14 +0200
commitc3ef1b5c1eaf1348a18d753dbf7bda3c129e3262 (patch)
treefeba26855cc7d26751415b96da1a17cdcbf09839
parent3b49e4d61e6f360f1c787aa048edf584aec55278 (diff)
downloadgitlab-c3ef1b5c1eaf1348a18d753dbf7bda3c129e3262.tar.gz
fix: add 52x range to retry transient failures and tests
-rw-r--r--gitlab/client.py9
-rw-r--r--tests/unit/test_gitlab_http_methods.py98
2 files changed, 103 insertions, 4 deletions
diff --git a/gitlab/client.py b/gitlab/client.py
index 75765f7..c6e9b96 100644
--- a/gitlab/client.py
+++ b/gitlab/client.py
@@ -35,6 +35,8 @@ REDIRECT_MSG = (
"{source!r} to {target!r}"
)
+RETRYABLE_TRANSIENT_ERROR_CODES = [500, 502, 503, 504] + list(range(520, 531))
+
class Gitlab:
"""Represents a GitLab server connection.
@@ -694,9 +696,9 @@ class Gitlab:
)
except requests.ConnectionError:
if retry_transient_errors and (
- max_retries == -1 or cur_retries < max_retries
+ max_retries == -1 or cur_retries < max_retries
):
- wait_time = 2 ** cur_retries * 0.1
+ wait_time = 2**cur_retries * 0.1
cur_retries += 1
time.sleep(wait_time)
continue
@@ -712,7 +714,8 @@ class Gitlab:
"retry_transient_errors", self.retry_transient_errors
)
if (429 == result.status_code and obey_rate_limit) or (
- result.status_code in [500, 502, 503, 504] and retry_transient_errors
+ result.status_code in RETRYABLE_TRANSIENT_ERROR_CODES
+ and retry_transient_errors
):
# Response headers documentation:
# https://docs.gitlab.com/ee/user/admin_area/settings/user_and_ip_rate_limits.html#response-headers
diff --git a/tests/unit/test_gitlab_http_methods.py b/tests/unit/test_gitlab_http_methods.py
index a65b53e..ed96215 100644
--- a/tests/unit/test_gitlab_http_methods.py
+++ b/tests/unit/test_gitlab_http_methods.py
@@ -3,6 +3,7 @@ import requests
import responses
from gitlab import GitlabHttpError, GitlabList, GitlabParsingError, RedirectError
+from gitlab.client import RETRYABLE_TRANSIENT_ERROR_CODES
from tests.unit import helpers
MATCH_EMPTY_QUERY_PARAMS = [responses.matchers.query_param_matcher({})]
@@ -51,7 +52,7 @@ def test_http_request_404(gl):
@responses.activate
-@pytest.mark.parametrize("status_code", [500, 502, 503, 504])
+@pytest.mark.parametrize("status_code", RETRYABLE_TRANSIENT_ERROR_CODES)
def test_http_request_with_only_failures(gl, status_code):
url = "http://localhost/api/v4/projects"
responses.add(
@@ -98,6 +99,37 @@ def test_http_request_with_retry_on_method_for_transient_failures(gl):
@responses.activate
+def test_http_request_with_retry_on_method_for_transient_network_failures(gl):
+ call_count = 0
+ calls_before_success = 3
+
+ url = "http://localhost/api/v4/projects"
+
+ def request_callback(request):
+ nonlocal call_count
+ call_count += 1
+ status_code = 200
+ headers = {}
+ body = "[]"
+
+ if call_count >= calls_before_success:
+ return (status_code, headers, body)
+ raise requests.ConnectionError("Connection aborted.")
+
+ responses.add_callback(
+ method=responses.GET,
+ url=url,
+ callback=request_callback,
+ content_type="application/json",
+ )
+
+ http_r = gl.http_request("get", "/projects", retry_transient_errors=True)
+
+ assert http_r.status_code == 200
+ assert len(responses.calls) == calls_before_success
+
+
+@responses.activate
def test_http_request_with_retry_on_class_for_transient_failures(gl_retry):
call_count = 0
calls_before_success = 3
@@ -127,6 +159,37 @@ def test_http_request_with_retry_on_class_for_transient_failures(gl_retry):
@responses.activate
+def test_http_request_with_retry_on_class_for_transient_network_failures(gl_retry):
+ call_count = 0
+ calls_before_success = 3
+
+ url = "http://localhost/api/v4/projects"
+
+ def request_callback(request: requests.models.PreparedRequest):
+ nonlocal call_count
+ call_count += 1
+ status_code = 200
+ headers = {}
+ body = "[]"
+
+ if call_count >= calls_before_success:
+ return (status_code, headers, body)
+ raise requests.ConnectionError("Connection aborted.")
+
+ responses.add_callback(
+ method=responses.GET,
+ url=url,
+ callback=request_callback,
+ content_type="application/json",
+ )
+
+ http_r = gl_retry.http_request("get", "/projects", retry_transient_errors=True)
+
+ assert http_r.status_code == 200
+ assert len(responses.calls) == calls_before_success
+
+
+@responses.activate
def test_http_request_with_retry_on_class_and_method_for_transient_failures(gl_retry):
call_count = 0
calls_before_success = 3
@@ -155,6 +218,39 @@ def test_http_request_with_retry_on_class_and_method_for_transient_failures(gl_r
assert len(responses.calls) == 1
+@responses.activate
+def test_http_request_with_retry_on_class_and_method_for_transient_network_failures(
+ gl_retry,
+):
+ call_count = 0
+ calls_before_success = 3
+
+ url = "http://localhost/api/v4/projects"
+
+ def request_callback(request):
+ nonlocal call_count
+ call_count += 1
+ status_code = 200
+ headers = {}
+ body = "[]"
+
+ if call_count >= calls_before_success:
+ return (status_code, headers, body)
+ raise requests.ConnectionError("Connection aborted.")
+
+ responses.add_callback(
+ method=responses.GET,
+ url=url,
+ callback=request_callback,
+ content_type="application/json",
+ )
+
+ with pytest.raises(requests.ConnectionError):
+ gl_retry.http_request("get", "/projects", retry_transient_errors=False)
+
+ assert len(responses.calls) == 1
+
+
def create_redirect_response(
*, response: requests.models.Response, http_method: str, api_path: str
) -> requests.models.Response: