|
| 1 | +import socket |
| 2 | +import http.client as httplib |
| 3 | +import xmlrpc.client |
| 4 | + |
| 5 | +import six.moves.xmlrpc_client as xmlrpclib |
| 6 | + |
1 | 7 | import pytest |
2 | 8 | import gssapi |
3 | 9 | import mock |
4 | 10 | import sys |
5 | 11 |
|
6 | | -from kobo.xmlrpc import SafeCookieTransport |
| 12 | +from kobo.xmlrpc import SafeCookieTransport, retry_request_decorator |
7 | 13 | from kobo.conf import PyConfigParser |
8 | 14 | from kobo.client import HubProxy |
9 | 15 |
|
@@ -36,6 +42,31 @@ def request(self, host, path, request, verbose=False): |
36 | 42 | return [] |
37 | 43 |
|
38 | 44 |
|
| 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 | + |
39 | 70 | def test_login_token_oidc(requests_session): |
40 | 71 | """Login with OIDC client credentials flow.""" |
41 | 72 |
|
@@ -252,3 +283,60 @@ def test_pass_transport_args(requests_session): |
252 | 283 | mock_transport_class.assert_called_with(context=mock.ANY, |
253 | 284 | retry_count=2, |
254 | 285 | 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