Skip to content
This repository was archived by the owner on Oct 27, 2025. It is now read-only.

Commit b14ee33

Browse files
committed
retries: enable retries on bad status codes
In addition to connection errors, urllib3.util.Retry will retry *only* for explicitly listed status codes Change default max-retry count to 10 (was 0) Set the same for the various `build_dci_context…` functions (was 80). The feature in responses that allows to test retries only exists in versions for python >= 3.7, run this test only for python3.9. Change-Id: I71c686a945eafefeb9b7e5ae1a5f97bcdb66811a Depends-On: https://softwarefactory-project.io/r/c/dci-control-server/+/32315
1 parent f8ab473 commit b14ee33

5 files changed

Lines changed: 74 additions & 23 deletions

File tree

dciclient/v1/api/context.py

Lines changed: 14 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323
import requests
2424
from requests.adapters import HTTPAdapter
2525
from requests.auth import AuthBase
26-
from requests.packages.urllib3.util.retry import Retry
26+
from urllib3.util.retry import Retry
2727

2828
from dciauth.v2.headers import generate_headers
2929
from dciclient import version
@@ -32,7 +32,7 @@
3232
class DciContextBase(object):
3333
API_VERSION = "api/v1"
3434

35-
def __init__(self, dci_cs_url, max_retries=0, user_agent=None):
35+
def __init__(self, dci_cs_url, max_retries=10, user_agent=None):
3636
self.session = self._build_http_session(user_agent, max_retries)
3737
self.dci_cs_api = "%s/%s" % (dci_cs_url, DciContext.API_VERSION)
3838
self.last_job_id = None
@@ -45,15 +45,20 @@ def _build_http_session(user_agent, max_retries):
4545
user_agent = "python-dciclient_%s" % version.__version__
4646
session.headers["User-Agent"] = user_agent
4747
session.headers["Client-Version"] = "python-dciclient_%s" % version.__version__
48-
retries = Retry(total=max_retries, backoff_factor=0.1)
48+
retries = Retry(
49+
total=max_retries,
50+
backoff_factor=0.2,
51+
status_forcelist=(413, 429, 500, 502, 503, 504),
52+
raise_on_status=False,
53+
)
4954
session.mount("http://", HTTPAdapter(max_retries=retries))
5055
session.mount("https://", HTTPAdapter(max_retries=retries))
5156

5257
return session
5358

5459

5560
class DciContext(DciContextBase):
56-
def __init__(self, dci_cs_url, login, password, max_retries=0, user_agent=None):
61+
def __init__(self, dci_cs_url, login, password, max_retries=10, user_agent=None):
5762
super(DciContext, self).__init__(
5863
dci_cs_url.rstrip("/"), max_retries, user_agent
5964
)
@@ -62,7 +67,7 @@ def __init__(self, dci_cs_url, login, password, max_retries=0, user_agent=None):
6267

6368

6469
def build_dci_context(
65-
dci_cs_url=None, dci_login=None, dci_password=None, user_agent=None, max_retries=80
70+
dci_cs_url=None, dci_login=None, dci_password=None, user_agent=None, max_retries=10
6671
):
6772
dci_cs_url = dci_cs_url or os.environ.get("DCI_CS_URL", "")
6873
dci_login = dci_login or os.environ.get("DCI_LOGIN", "")
@@ -126,7 +131,7 @@ def get_body(self, body):
126131

127132
class DciSignatureContext(DciContextBase):
128133
def __init__(
129-
self, dci_cs_url, client_id, api_secret, max_retries=0, user_agent=None
134+
self, dci_cs_url, client_id, api_secret, max_retries=10, user_agent=None
130135
):
131136
super(DciSignatureContext, self).__init__(
132137
dci_cs_url.rstrip("/"), max_retries, user_agent
@@ -139,7 +144,7 @@ def build_signature_context(
139144
dci_client_id=None,
140145
dci_api_secret=None,
141146
user_agent=None,
142-
max_retries=80,
147+
max_retries=10,
143148
):
144149
dci_cs_url = dci_cs_url or os.environ.get("DCI_CS_URL", "")
145150
dci_client_id = dci_client_id or os.environ.get("DCI_CLIENT_ID", "")
@@ -161,7 +166,7 @@ def build_signature_context(
161166

162167

163168
class SsoContext(DciContextBase):
164-
def __init__(self, dci_cs_url, token, max_retries=0, user_agent=None):
169+
def __init__(self, dci_cs_url, token, max_retries=10, user_agent=None):
165170
super(SsoContext, self).__init__(
166171
dci_cs_url.rstrip("/"), max_retries, user_agent
167172
)
@@ -174,7 +179,7 @@ def build_sso_context(
174179
username,
175180
password,
176181
token,
177-
max_retries=0,
182+
max_retries=10,
178183
user_agent=None,
179184
refresh=False,
180185
):

test-requirements.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,4 @@ pytest
33
mock
44
boto3
55
ansible==2.9.27
6+
responses

tests/shell_commands/test_cli.py

Lines changed: 8 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -15,27 +15,24 @@
1515
# under the License.
1616

1717
import os
18-
from mock import patch
1918
from pytest import raises
2019
from dciclient.v1.shell_commands.cli import parse_arguments
2120
from dciclient.v1.shell_commands.context import _default_dci_cs_url
2221
from dciclient.version import __version__
2322

2423

2524
def test_parse_arguments_version(capsys):
26-
try:
25+
with raises(SystemExit):
2726
parse_arguments(["--version"])
28-
except SystemExit:
29-
captured = capsys.readouterr()
30-
assert captured.out == "dcictl {}\n".format(__version__)
27+
captured = capsys.readouterr()
28+
assert captured.out == "dcictl {}\n".format(__version__)
3129

3230

33-
@patch("sys.exit")
34-
def test_parse_no_command_print_help(exit_function, capsys):
35-
parse_arguments([])
31+
def test_parse_no_command_print_help(capsys):
32+
with raises(SystemExit):
33+
parse_arguments([])
3634
captured = capsys.readouterr()
3735
assert "usage: dcictl" in captured.out
38-
assert exit_function.called
3936

4037

4138
def test_parse_arguments_format():
@@ -136,9 +133,8 @@ def test_parse_arguments_dci_cs_url_overload_from_env():
136133

137134

138135
# fear test
139-
@patch("sys.exit")
140-
def test_parse_arguments_user_create_mutually_exclusive_boolean_flags(exit_function):
141-
with raises(TypeError):
136+
def test_parse_arguments_user_create_mutually_exclusive_boolean_flags():
137+
with raises(SystemExit):
142138
parse_arguments(
143139
[
144140
"user-create",
@@ -152,7 +148,6 @@ def test_parse_arguments_user_create_mutually_exclusive_boolean_flags(exit_funct
152148
"--no-active",
153149
]
154150
)
155-
assert exit_function.called
156151

157152

158153
def test_parse_arguments_sso():

tests/test_context.py

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
# -*- encoding: utf-8 -*-
2+
#
3+
# Copyright 2024 Red Hat, Inc.
4+
#
5+
# Licensed under the Apache License, Version 2.0 (the "License"); you may
6+
# not use this file except in compliance with the License. You may obtain
7+
# a copy of the License at
8+
#
9+
# http://www.apache.org/licenses/LICENSE-2.0
10+
#
11+
# Unless required by applicable law or agreed to in writing, software
12+
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
13+
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
14+
# License for the specific language governing permissions and limitations
15+
# under the License.
16+
17+
import sys
18+
19+
import pytest
20+
import responses
21+
22+
from dciclient.v1.api import context as dci_context
23+
24+
25+
@responses.activate
26+
def test_context_calls_once_on_status_ok():
27+
api_ctx = dci_context.DciContextBase(dci_cs_url="http://dciapi", max_retries=4)
28+
responses.add(method=responses.GET, url=api_ctx.dci_cs_api, status=200)
29+
30+
r = api_ctx.session.get(api_ctx.dci_cs_api)
31+
assert r.status_code == 200
32+
assert responses.assert_call_count(api_ctx.dci_cs_api, 1)
33+
34+
35+
@pytest.mark.xfail(
36+
sys.version_info < (3, 9), reason="This test is expected to fail before python 3.8"
37+
)
38+
@responses.activate
39+
def test_context_retries_on_status_bad():
40+
api_ctx = dci_context.DciContextBase(dci_cs_url="http://dci/api/v1")
41+
responses.add(method=responses.GET, url="http://dci/api/v1", status=500)
42+
43+
r = api_ctx.session.get("http://dci/api/v1")
44+
assert r.status_code == 500
45+
assert responses.assert_call_count("http://dci/api/v1", 11)

tox.ini

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[tox]
22
skipsdist = True
3-
envlist = pep8,py36
3+
envlist = pep8,py36,py39
44

55
[testenv]
66
usedevelop = True
@@ -21,6 +21,11 @@ commands =
2121
{[testenv]commands}
2222
py.test -v {posargs}
2323

24+
[testenv:py39]
25+
commands =
26+
{[testenv]commands}
27+
py.test -v {posargs}
28+
2429
[testenv:pep8]
2530
skip_install = true
2631
deps = flake8

0 commit comments

Comments
 (0)