# HG changeset patch # User Marcin Kuzminski # Date 2017-02-07 15:16:42 # Node ID a4133cfc6852be3f55f4b3cb0c567e161321d5ce # Parent c1ce56be690e75820a31dfc852ab2d27b3aeab6a http-backend: catch errors from HTTP calls that are not raising exceptions, but return non 200 exception codes. diff --git a/rhodecode/lib/vcs/__init__.py b/rhodecode/lib/vcs/__init__.py --- a/rhodecode/lib/vcs/__init__.py +++ b/rhodecode/lib/vcs/__init__.py @@ -216,7 +216,9 @@ class CurlSession(object): curl.setopt(curl.WRITEDATA, response_buffer) curl.perform() - return CurlResponse(response_buffer) + status_code = curl.getinfo(pycurl.HTTP_CODE) + + return CurlResponse(response_buffer, status_code) class CurlResponse(object): @@ -228,13 +230,18 @@ class CurlResponse(object): `requests` as a drop in replacement for benchmarking purposes. """ - def __init__(self, response_buffer): + def __init__(self, response_buffer, status_code): self._response_buffer = response_buffer + self._status_code = status_code @property def content(self): return self._response_buffer.getvalue() + @property + def status_code(self): + return self._status_code + def _create_http_rpc_session(): session = CurlSession() diff --git a/rhodecode/lib/vcs/client_http.py b/rhodecode/lib/vcs/client_http.py --- a/rhodecode/lib/vcs/client_http.py +++ b/rhodecode/lib/vcs/client_http.py @@ -196,10 +196,15 @@ def _remote_call(url, payload, exception except pycurl.error as e: raise exceptions.HttpVCSCommunicationError(e) + if response.status_code >= 400: + log.error('Call to %s returned non 200 HTTP code: %s', + url, response.status_code) + raise exceptions.HttpVCSCommunicationError(repr(response.content)) + try: response = msgpack.unpackb(response.content) except Exception: - log.exception('Failed to decode repsponse %r', response.content) + log.exception('Failed to decode response %r', response.content) raise error = response.get('error') diff --git a/rhodecode/tests/vcs/test_client_http.py b/rhodecode/tests/vcs/test_client_http.py --- a/rhodecode/tests/vcs/test_client_http.py +++ b/rhodecode/tests/vcs/test_client_http.py @@ -25,20 +25,7 @@ import msgpack import pytest from rhodecode.lib import vcs -from rhodecode.lib.vcs import client_http - - -def test_uses_persistent_http_connections(caplog, vcsbackend_hg): - repo = vcsbackend_hg.repo - remote_call = repo._remote.branches - - with caplog.at_level(logging.INFO): - for x in range(5): - remote_call(normal=True, closed=False) - - new_connections = [ - r for r in caplog.record_tuples if is_new_connection(*r)] - assert len(new_connections) <= 1 +from rhodecode.lib.vcs import client_http, exceptions def is_new_connection(logger, level, message): @@ -53,7 +40,24 @@ def stub_session(): Stub of `requests.Session()`. """ session = mock.Mock() - session.post().content = msgpack.packb({}) + post = session.post() + post.content = msgpack.packb({}) + post.status_code = 200 + + session.reset_mock() + return session + + +@pytest.fixture +def stub_fail_session(): + """ + Stub of `requests.Session()`. + """ + session = mock.Mock() + post = session.post() + post.content = msgpack.packb({'error': '500'}) + post.status_code = 500 + session.reset_mock() return session @@ -68,6 +72,29 @@ def stub_session_factory(stub_session): return session_factory +@pytest.fixture +def stub_session_failing_factory(stub_fail_session): + """ + Stub of `rhodecode.lib.vcs.client_http.ThreadlocalSessionFactory`. + """ + session_factory = mock.Mock() + session_factory.return_value = stub_fail_session + return session_factory + + +def test_uses_persistent_http_connections(caplog, vcsbackend_hg): + repo = vcsbackend_hg.repo + remote_call = repo._remote.branches + + with caplog.at_level(logging.INFO): + for x in range(5): + remote_call(normal=True, closed=False) + + new_connections = [ + r for r in caplog.record_tuples if is_new_connection(*r)] + assert len(new_connections) <= 1 + + def test_repo_maker_uses_session_for_classmethods(stub_session_factory): repo_maker = client_http.RepoMaker( 'server_and_port', 'endpoint', 'test_dummy_scm', stub_session_factory) @@ -94,3 +121,13 @@ def test_connect_passes_in_the_same_sess session_factory.return_value = stub_session vcs.connect_http('server_and_port') + + +def test_repo_maker_uses_session_that_throws_error( + stub_session_failing_factory, config): + repo_maker = client_http.RepoMaker( + 'server_and_port', 'endpoint', 'test_dummy_scm', stub_session_failing_factory) + repo = repo_maker('stub_path', config) + + with pytest.raises(exceptions.HttpVCSCommunicationError): + repo.example_call()