Show More
@@ -71,7 +71,7 b' def VcsException(org_exc=None):' | |||||
71 | return _make_exception_wrapper |
|
71 | return _make_exception_wrapper | |
72 |
|
72 | |||
73 |
|
73 | |||
74 |
def |
|
74 | def LockedRepoException(org_exc=None): | |
75 | def _make_exception_wrapper(*args): |
|
75 | def _make_exception_wrapper(*args): | |
76 | return _make_exception('repo_locked', org_exc, *args) |
|
76 | return _make_exception('repo_locked', org_exc, *args) | |
77 | return _make_exception_wrapper |
|
77 | return _make_exception_wrapper |
@@ -41,54 +41,6 b" celery_app = Celery('__vcsserver__')" | |||||
41 | log = logging.getLogger(__name__) |
|
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 | class HooksCeleryClient: |
|
44 | class HooksCeleryClient: | |
93 | TASK_TIMEOUT = 60 # time in seconds |
|
45 | TASK_TIMEOUT = 60 # time in seconds | |
94 |
|
46 | |||
@@ -160,17 +112,20 b' class SvnMessageWriter(RemoteMessageWrit' | |||||
160 |
|
112 | |||
161 |
|
113 | |||
162 | def _maybe_handle_exception(result): |
|
114 | def _maybe_handle_exception(result): | |
|
115 | ||||
|
116 | ||||
163 | exception_class = result.get('exception') |
|
117 | exception_class = result.get('exception') | |
164 | exception_traceback = result.get('exception_traceback') |
|
118 | exception_traceback = result.get('exception_traceback') | |
165 |
if not |
|
119 | if not exception_class: | |
166 | return |
|
120 | return | |
|
121 | ||||
167 | log.debug('Handling hook-call exception: %s', exception_class) |
|
122 | log.debug('Handling hook-call exception: %s', exception_class) | |
168 |
|
123 | |||
169 | if exception_traceback: |
|
124 | if exception_traceback: | |
170 | log.error('Got traceback from remote call:%s', exception_traceback) |
|
125 | log.error('Got traceback from remote call:%s', exception_traceback) | |
171 |
|
126 | |||
172 |
if exception_class == 'HTTPLockedR |
|
127 | if exception_class == 'HTTPLockedRepo': | |
173 |
raise exceptions. |
|
128 | raise exceptions.LockedRepoException()(*result['exception_args']) | |
174 | elif exception_class == 'ClientNotSupportedError': |
|
129 | elif exception_class == 'ClientNotSupportedError': | |
175 | raise exceptions.ClientNotSupportedException()(*result['exception_args']) |
|
130 | raise exceptions.ClientNotSupportedException()(*result['exception_args']) | |
176 | elif exception_class == 'HTTPBranchProtected': |
|
131 | elif exception_class == 'HTTPBranchProtected': | |
@@ -184,14 +139,11 b' def _maybe_handle_exception(result):' | |||||
184 |
|
139 | |||
185 |
|
140 | |||
186 | def _get_hooks_client(extras): |
|
141 | def _get_hooks_client(extras): | |
187 | hooks_uri = extras.get('hooks_uri') |
|
|||
188 | task_queue = extras.get('task_queue') |
|
142 | task_queue = extras.get('task_queue') | |
189 | task_backend = extras.get('task_backend') |
|
143 | task_backend = extras.get('task_backend') | |
190 | is_shadow_repo = extras.get('is_shadow_repo') |
|
144 | is_shadow_repo = extras.get('is_shadow_repo') | |
191 |
|
145 | |||
192 | if hooks_uri: |
|
146 | if task_queue and task_backend: | |
193 | return HooksHttpClient(hooks_uri) |
|
|||
194 | elif task_queue and task_backend: |
|
|||
195 | return HooksCeleryClient(task_queue, task_backend) |
|
147 | return HooksCeleryClient(task_queue, task_backend) | |
196 | elif is_shadow_repo: |
|
148 | elif is_shadow_repo: | |
197 | return HooksShadowRepoClient() |
|
149 | return HooksShadowRepoClient() |
@@ -15,17 +15,11 b'' | |||||
15 | # along with this program; if not, write to the Free Software Foundation, |
|
15 | # along with this program; if not, write to the Free Software Foundation, | |
16 | # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA |
|
16 | # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA | |
17 |
|
17 | |||
18 |
import |
|
18 | import pytest | |
19 | import msgpack |
|
|||
20 |
|
||||
21 | from http.server import BaseHTTPRequestHandler |
|
|||
22 | from socketserver import TCPServer |
|
|||
23 |
|
19 | |||
24 | import mercurial.ui |
|
20 | import mercurial.ui | |
25 | import mock |
|
21 | import mock | |
26 | import pytest |
|
|||
27 |
|
22 | |||
28 | from vcsserver.hooks import HooksHttpClient |
|
|||
29 | from vcsserver.lib.ext_json import json |
|
23 | from vcsserver.lib.ext_json import json | |
30 | from vcsserver import hooks |
|
24 | from vcsserver import hooks | |
31 |
|
25 | |||
@@ -41,7 +35,6 b' def get_hg_ui(extras=None):' | |||||
41 | 'make_lock': '', |
|
35 | 'make_lock': '', | |
42 | 'action': '', |
|
36 | 'action': '', | |
43 | 'ip': '', |
|
37 | 'ip': '', | |
44 | 'hooks_uri': 'fake_hooks_uri', |
|
|||
45 | } |
|
38 | } | |
46 | required_extras.update(extras) |
|
39 | required_extras.update(extras) | |
47 | hg_ui = mercurial.ui.ui() |
|
40 | hg_ui = mercurial.ui.ui() | |
@@ -123,15 +116,6 b' def test_git_post_pull_is_disabled():' | |||||
123 |
|
116 | |||
124 | class TestGetHooksClient: |
|
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 | def test_return_celery_client_when_queue_and_backend_provided(self): |
|
119 | def test_return_celery_client_when_queue_and_backend_provided(self): | |
136 | task_queue = 'redis://task_queue:0' |
|
120 | task_queue = 'redis://task_queue:0' | |
137 | task_backend = task_queue |
|
121 | task_backend = task_queue | |
@@ -142,116 +126,10 b' class TestGetHooksClient:' | |||||
142 | assert isinstance(result, hooks.HooksCeleryClient) |
|
126 | assert isinstance(result, hooks.HooksCeleryClient) | |
143 |
|
127 | |||
144 |
|
128 | |||
145 |
class TestHooks |
|
129 | class TestHooksCeleryClient: | |
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 |
|
|||
201 |
|
130 | |||
202 |
def __init |
|
131 | def test_hooks_http_client_init(self): | |
203 | self._daemon = TCPServer((self.ip_address, 0), MirrorHttpHandler) |
|
132 | queue = 'redis://redis:6379/0' | |
204 | _, self.port = self._daemon.server_address |
|
133 | backend = 'redis://redis:6379/0' | |
205 | self._thread = threading.Thread(target=self._daemon.serve_forever) |
|
134 | client = hooks.HooksCeleryClient(queue, backend) | |
206 | self._thread.daemon = True |
|
135 | assert client.celery_app.conf.broker_url == queue | |
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} |
|
General Comments 0
You need to be logged in to leave comments.
Login now