Skip to content

Commit 79c8169

Browse files
authored
Merge pull request #272 from amcmahon-rh/RHELDST-30181
Add XMLRPC Fault exception to retry decorator
2 parents d890be7 + e72852c commit 79c8169

File tree

2 files changed

+94
-2
lines changed

2 files changed

+94
-2
lines changed

kobo/xmlrpc.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -479,7 +479,11 @@ def request(self, *args, **kwargs):
479479
except KeyboardInterrupt:
480480
raise
481481
except (socket.error, socket.herror, socket.gaierror, socket.timeout,
482-
httplib.CannotSendRequest, xmlrpclib.ProtocolError) as ex:
482+
httplib.CannotSendRequest, xmlrpclib.ProtocolError,
483+
xmlrpclib.Fault) as ex:
484+
if type(ex) is xmlrpclib.Fault and \
485+
"PermissionDenied" not in ex.faultString:
486+
raise
483487
if i >= self.retry_count:
484488
raise
485489
retries_left = self.retry_count - i

tests/test_hubproxy.py

Lines changed: 89 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,15 @@
1+
import socket
2+
import http.client as httplib
3+
import xmlrpc.client
4+
5+
import six.moves.xmlrpc_client as xmlrpclib
6+
17
import pytest
28
import gssapi
39
import mock
410
import sys
511

6-
from kobo.xmlrpc import SafeCookieTransport
12+
from kobo.xmlrpc import SafeCookieTransport, retry_request_decorator
713
from kobo.conf import PyConfigParser
814
from kobo.client import HubProxy
915

@@ -36,6 +42,31 @@ def request(self, host, path, request, verbose=False):
3642
return []
3743

3844

45+
def error_raising_transport(exception, bad_function, exception_args=(),
46+
exception_kwargs={}):
47+
# Fake Transport class that raises specific exceptions.
48+
class FakeTransport(SafeCookieTransport):
49+
"""A fake XML-RPC transport where every request succeeds without doing anything.
50+
51+
Subclasses the real SafeCookieTransport so we get a real CookieJar.
52+
"""
53+
54+
def __init__(self, *args, **kwargs):
55+
super().__init__(*args, **kwargs)
56+
self.exception_count = 0
57+
58+
def request(self, host, path, request, verbose=False):
59+
# Only raise error for a specific faulty function. Hub proxy makes
60+
# a lot of requests during init we want to allow. Request is binary
61+
# xml which should contain our function as a string
62+
if bad_function in request:
63+
self.exception_count += 1
64+
raise exception(*exception_args, **exception_kwargs)
65+
66+
return []
67+
68+
return FakeTransport
69+
3970
def test_login_token_oidc(requests_session):
4071
"""Login with OIDC client credentials flow."""
4172

@@ -252,3 +283,60 @@ def test_pass_transport_args(requests_session):
252283
mock_transport_class.assert_called_with(context=mock.ANY,
253284
retry_count=2,
254285
retry_timeout=45)
286+
287+
288+
@pytest.mark.parametrize("exception, exception_args, exception_kwargs",
289+
[(socket.error, (), {}),
290+
(httplib.CannotSendRequest, (), {}),
291+
(xmlrpclib.Fault, ["1", "PermissionDenied"], {})]
292+
)
293+
def test_proxy_retries_on_error(requests_session, capsys, exception, exception_args, exception_kwargs):
294+
"""HubProxy proxy retry class captures exceptions"""
295+
retry_count = 2
296+
conf = PyConfigParser()
297+
conf.load_from_dict({"HUB_URL": 'https://example.com/hub'})
298+
TransportClass = retry_request_decorator(
299+
error_raising_transport(exception, b"faulty.function", exception_args, exception_kwargs)
300+
)
301+
transport = TransportClass(retry_count=retry_count, retry_timeout=1)
302+
proxy = HubProxy(conf, transport=transport)
303+
304+
with pytest.raises(exception):
305+
proxy.faulty.function()
306+
307+
assert transport.exception_count == retry_count + 1
308+
captured = capsys.readouterr()
309+
assert captured.err.count("XML-RPC connection to example.com failed") == retry_count
310+
311+
312+
313+
@pytest.mark.parametrize("exception_string, retried",
314+
[("PermissionDenied: Login required.", True),
315+
("SomeOtherError: hub broke.", False)]
316+
)
317+
def test_proxy_xmlrpc_fault(requests_session, capsys, exception_string, retried):
318+
"""HubProxy proxy retries xmlrpc fault on PermissionDenied errors"""
319+
retry_count = 2
320+
conf = PyConfigParser()
321+
conf.load_from_dict({"HUB_URL": 'https://example.com/hub'})
322+
TransportClass = retry_request_decorator(
323+
error_raising_transport(xmlrpc.client.Fault,
324+
b"faulty.function",
325+
[1, exception_string],
326+
{})
327+
)
328+
transport = TransportClass(retry_count=retry_count, retry_timeout=1)
329+
proxy = HubProxy(conf, transport=transport)
330+
331+
with pytest.raises(xmlrpc.client.Fault):
332+
proxy.faulty.function()
333+
334+
if retried:
335+
assert transport.exception_count == retry_count + 1
336+
captured = capsys.readouterr()
337+
assert captured.err.count("XML-RPC connection to example.com failed") == retry_count
338+
else:
339+
assert transport.exception_count == 1
340+
captured = capsys.readouterr()
341+
assert captured.err.count(
342+
"XML-RPC connection to example.com failed") == 0

0 commit comments

Comments
 (0)