Show More
@@ -71,7 +71,7 b' def VcsException(org_exc=None):' | |||
|
71 | 71 | return _make_exception_wrapper |
|
72 | 72 | |
|
73 | 73 | |
|
74 |
def |
|
|
74 | def LockedRepoException(org_exc=None): | |
|
75 | 75 | def _make_exception_wrapper(*args): |
|
76 | 76 | return _make_exception('repo_locked', org_exc, *args) |
|
77 | 77 | return _make_exception_wrapper |
@@ -41,54 +41,6 b" celery_app = Celery('__vcsserver__')" | |||
|
41 | 41 | log = logging.getLogger(__name__) |
|
42 | 42 | |
|
43 | 43 | |
|
44 | class HooksHttpClient: | |
|
45 | proto = 'msgpack.v1' | |
|
46 | connection = None | |
|
47 | ||
|
48 | def __init__(self, hooks_uri): | |
|
49 | self.hooks_uri = hooks_uri | |
|
50 | ||
|
51 | def __repr__(self): | |
|
52 | return f'{self.__class__}(hook_uri={self.hooks_uri}, proto={self.proto})' | |
|
53 | ||
|
54 | def __call__(self, method, extras): | |
|
55 | connection = http.client.HTTPConnection(self.hooks_uri) | |
|
56 | # binary msgpack body | |
|
57 | headers, body = self._serialize(method, extras) | |
|
58 | log.debug('Doing a new hooks call using HTTPConnection to %s', self.hooks_uri) | |
|
59 | ||
|
60 | try: | |
|
61 | try: | |
|
62 | connection.request('POST', '/', body, headers) | |
|
63 | except Exception as error: | |
|
64 | log.error('Hooks calling Connection failed on %s, org error: %s', connection.__dict__, error) | |
|
65 | raise | |
|
66 | ||
|
67 | response = connection.getresponse() | |
|
68 | try: | |
|
69 | return msgpack.load(response) | |
|
70 | except Exception: | |
|
71 | response_data = response.read() | |
|
72 | log.exception('Failed to decode hook response json data. ' | |
|
73 | 'response_code:%s, raw_data:%s', | |
|
74 | response.status, response_data) | |
|
75 | raise | |
|
76 | finally: | |
|
77 | connection.close() | |
|
78 | ||
|
79 | @classmethod | |
|
80 | def _serialize(cls, hook_name, extras): | |
|
81 | data = { | |
|
82 | 'method': hook_name, | |
|
83 | 'extras': extras | |
|
84 | } | |
|
85 | headers = { | |
|
86 | "rc-hooks-protocol": cls.proto, | |
|
87 | "Connection": "keep-alive" | |
|
88 | } | |
|
89 | return headers, msgpack.packb(data) | |
|
90 | ||
|
91 | ||
|
92 | 44 | class HooksCeleryClient: |
|
93 | 45 | TASK_TIMEOUT = 60 # time in seconds |
|
94 | 46 | |
@@ -160,17 +112,20 b' class SvnMessageWriter(RemoteMessageWrit' | |||
|
160 | 112 | |
|
161 | 113 | |
|
162 | 114 | def _maybe_handle_exception(result): |
|
115 | ||
|
116 | ||
|
163 | 117 | exception_class = result.get('exception') |
|
164 | 118 | exception_traceback = result.get('exception_traceback') |
|
165 |
if not |
|
|
119 | if not exception_class: | |
|
166 | 120 | return |
|
121 | ||
|
167 | 122 | log.debug('Handling hook-call exception: %s', exception_class) |
|
168 | 123 | |
|
169 | 124 | if exception_traceback: |
|
170 | 125 | log.error('Got traceback from remote call:%s', exception_traceback) |
|
171 | 126 | |
|
172 |
if exception_class == 'HTTPLockedR |
|
|
173 |
raise exceptions. |
|
|
127 | if exception_class == 'HTTPLockedRepo': | |
|
128 | raise exceptions.LockedRepoException()(*result['exception_args']) | |
|
174 | 129 | elif exception_class == 'ClientNotSupportedError': |
|
175 | 130 | raise exceptions.ClientNotSupportedException()(*result['exception_args']) |
|
176 | 131 | elif exception_class == 'HTTPBranchProtected': |
@@ -184,14 +139,11 b' def _maybe_handle_exception(result):' | |||
|
184 | 139 | |
|
185 | 140 | |
|
186 | 141 | def _get_hooks_client(extras): |
|
187 | hooks_uri = extras.get('hooks_uri') | |
|
188 | 142 | task_queue = extras.get('task_queue') |
|
189 | 143 | task_backend = extras.get('task_backend') |
|
190 | 144 | is_shadow_repo = extras.get('is_shadow_repo') |
|
191 | 145 | |
|
192 | if hooks_uri: | |
|
193 | return HooksHttpClient(hooks_uri) | |
|
194 | elif task_queue and task_backend: | |
|
146 | if task_queue and task_backend: | |
|
195 | 147 | return HooksCeleryClient(task_queue, task_backend) |
|
196 | 148 | elif is_shadow_repo: |
|
197 | 149 | return HooksShadowRepoClient() |
@@ -15,17 +15,11 b'' | |||
|
15 | 15 | # along with this program; if not, write to the Free Software Foundation, |
|
16 | 16 | # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA |
|
17 | 17 | |
|
18 |
import |
|
|
19 | import msgpack | |
|
20 | ||
|
21 | from http.server import BaseHTTPRequestHandler | |
|
22 | from socketserver import TCPServer | |
|
18 | import pytest | |
|
23 | 19 | |
|
24 | 20 | import mercurial.ui |
|
25 | 21 | import mock |
|
26 | import pytest | |
|
27 | 22 | |
|
28 | from vcsserver.hooks import HooksHttpClient | |
|
29 | 23 | from vcsserver.lib.ext_json import json |
|
30 | 24 | from vcsserver import hooks |
|
31 | 25 | |
@@ -41,7 +35,6 b' def get_hg_ui(extras=None):' | |||
|
41 | 35 | 'make_lock': '', |
|
42 | 36 | 'action': '', |
|
43 | 37 | 'ip': '', |
|
44 | 'hooks_uri': 'fake_hooks_uri', | |
|
45 | 38 | } |
|
46 | 39 | required_extras.update(extras) |
|
47 | 40 | hg_ui = mercurial.ui.ui() |
@@ -123,15 +116,6 b' def test_git_post_pull_is_disabled():' | |||
|
123 | 116 | |
|
124 | 117 | class TestGetHooksClient: |
|
125 | 118 | |
|
126 | def test_returns_http_client_when_protocol_matches(self): | |
|
127 | hooks_uri = 'localhost:8000' | |
|
128 | result = hooks._get_hooks_client({ | |
|
129 | 'hooks_uri': hooks_uri, | |
|
130 | 'hooks_protocol': 'http' | |
|
131 | }) | |
|
132 | assert isinstance(result, hooks.HooksHttpClient) | |
|
133 | assert result.hooks_uri == hooks_uri | |
|
134 | ||
|
135 | 119 | def test_return_celery_client_when_queue_and_backend_provided(self): |
|
136 | 120 | task_queue = 'redis://task_queue:0' |
|
137 | 121 | task_backend = task_queue |
@@ -142,116 +126,10 b' class TestGetHooksClient:' | |||
|
142 | 126 | assert isinstance(result, hooks.HooksCeleryClient) |
|
143 | 127 | |
|
144 | 128 | |
|
145 |
class TestHooks |
|
|
146 | def test_init_sets_hooks_uri(self): | |
|
147 | uri = 'localhost:3000' | |
|
148 | client = hooks.HooksHttpClient(uri) | |
|
149 | assert client.hooks_uri == uri | |
|
150 | ||
|
151 | def test_serialize_returns_serialized_string(self): | |
|
152 | client = hooks.HooksHttpClient('localhost:3000') | |
|
153 | hook_name = 'test' | |
|
154 | extras = { | |
|
155 | 'first': 1, | |
|
156 | 'second': 'two' | |
|
157 | } | |
|
158 | hooks_proto, result = client._serialize(hook_name, extras) | |
|
159 | expected_result = msgpack.packb({ | |
|
160 | 'method': hook_name, | |
|
161 | 'extras': extras, | |
|
162 | }) | |
|
163 | assert hooks_proto == {'rc-hooks-protocol': 'msgpack.v1', 'Connection': 'keep-alive'} | |
|
164 | assert result == expected_result | |
|
165 | ||
|
166 | def test_call_queries_http_server(self, http_mirror): | |
|
167 | client = hooks.HooksHttpClient(http_mirror.uri) | |
|
168 | hook_name = 'test' | |
|
169 | extras = { | |
|
170 | 'first': 1, | |
|
171 | 'second': 'two' | |
|
172 | } | |
|
173 | result = client(hook_name, extras) | |
|
174 | expected_result = msgpack.unpackb(msgpack.packb({ | |
|
175 | 'method': hook_name, | |
|
176 | 'extras': extras | |
|
177 | }), raw=False) | |
|
178 | assert result == expected_result | |
|
179 | ||
|
180 | ||
|
181 | @pytest.fixture | |
|
182 | def http_mirror(request): | |
|
183 | server = MirrorHttpServer() | |
|
184 | request.addfinalizer(server.stop) | |
|
185 | return server | |
|
186 | ||
|
187 | ||
|
188 | class MirrorHttpHandler(BaseHTTPRequestHandler): | |
|
189 | ||
|
190 | def do_POST(self): | |
|
191 | length = int(self.headers['Content-Length']) | |
|
192 | body = self.rfile.read(length) | |
|
193 | self.send_response(200) | |
|
194 | self.end_headers() | |
|
195 | self.wfile.write(body) | |
|
196 | ||
|
197 | ||
|
198 | class MirrorHttpServer: | |
|
199 | ip_address = '127.0.0.1' | |
|
200 | port = 0 | |
|
129 | class TestHooksCeleryClient: | |
|
201 | 130 | |
|
202 |
def __init |
|
|
203 | self._daemon = TCPServer((self.ip_address, 0), MirrorHttpHandler) | |
|
204 | _, self.port = self._daemon.server_address | |
|
205 | self._thread = threading.Thread(target=self._daemon.serve_forever) | |
|
206 | self._thread.daemon = True | |
|
207 | self._thread.start() | |
|
208 | ||
|
209 | def stop(self): | |
|
210 | self._daemon.shutdown() | |
|
211 | self._thread.join() | |
|
212 | self._daemon = None | |
|
213 | self._thread = None | |
|
214 | ||
|
215 | @property | |
|
216 | def uri(self): | |
|
217 | return '{}:{}'.format(self.ip_address, self.port) | |
|
218 | ||
|
219 | ||
|
220 | def test_hooks_http_client_init(): | |
|
221 | hooks_uri = 'http://localhost:8000' | |
|
222 | client = HooksHttpClient(hooks_uri) | |
|
223 | assert client.hooks_uri == hooks_uri | |
|
224 | ||
|
225 | ||
|
226 | def test_hooks_http_client_call(): | |
|
227 | hooks_uri = 'http://localhost:8000' | |
|
228 | ||
|
229 | method = 'test_method' | |
|
230 | extras = {'key': 'value'} | |
|
231 | ||
|
232 | with \ | |
|
233 | mock.patch('http.client.HTTPConnection') as mock_connection,\ | |
|
234 | mock.patch('msgpack.load') as mock_load: | |
|
235 | ||
|
236 | client = HooksHttpClient(hooks_uri) | |
|
237 | ||
|
238 | mock_load.return_value = {'result': 'success'} | |
|
239 | response = mock.MagicMock() | |
|
240 | response.status = 200 | |
|
241 | mock_connection.request.side_effect = None | |
|
242 | mock_connection.getresponse.return_value = response | |
|
243 | ||
|
244 | result = client(method, extras) | |
|
245 | ||
|
246 | mock_connection.assert_called_with(hooks_uri) | |
|
247 | mock_connection.return_value.request.assert_called_once() | |
|
248 | assert result == {'result': 'success'} | |
|
249 | ||
|
250 | ||
|
251 | def test_hooks_http_client_serialize(): | |
|
252 | method = 'test_method' | |
|
253 | extras = {'key': 'value'} | |
|
254 | headers, body = HooksHttpClient._serialize(method, extras) | |
|
255 | ||
|
256 | assert headers == {'rc-hooks-protocol': HooksHttpClient.proto, 'Connection': 'keep-alive'} | |
|
257 | assert msgpack.unpackb(body) == {'method': method, 'extras': extras} | |
|
131 | def test_hooks_http_client_init(self): | |
|
132 | queue = 'redis://redis:6379/0' | |
|
133 | backend = 'redis://redis:6379/0' | |
|
134 | client = hooks.HooksCeleryClient(queue, backend) | |
|
135 | assert client.celery_app.conf.broker_url == queue |
General Comments 0
You need to be logged in to leave comments.
Login now