##// END OF EJS Templates
http: added service backend for testing communication, and extracting vcsserver version...
marcink -
r102:da6339df default
parent child Browse files
Show More
@@ -1,359 +1,376 b''
1 1 # RhodeCode VCSServer provides access to different vcs backends via network.
2 2 # Copyright (C) 2014-2016 RodeCode GmbH
3 3 #
4 4 # This program is free software; you can redistribute it and/or modify
5 5 # it under the terms of the GNU General Public License as published by
6 6 # the Free Software Foundation; either version 3 of the License, or
7 7 # (at your option) any later version.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU General Public License
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 18 import base64
19 19 import locale
20 20 import logging
21 21 import uuid
22 22 import wsgiref.util
23 23 from itertools import chain
24 24
25 25 import msgpack
26 26 from beaker.cache import CacheManager
27 27 from beaker.util import parse_cache_config_options
28 28 from pyramid.config import Configurator
29 29 from pyramid.wsgi import wsgiapp
30 30
31 31 from vcsserver import remote_wsgi, scm_app, settings, hgpatches
32 32 from vcsserver.echo_stub import remote_wsgi as remote_wsgi_stub
33 33 from vcsserver.echo_stub.echo_app import EchoApp
34 34 from vcsserver.exceptions import HTTPRepoLocked
35 35 from vcsserver.server import VcsServer
36 36
37 37 try:
38 38 from vcsserver.git import GitFactory, GitRemote
39 39 except ImportError:
40 40 GitFactory = None
41 41 GitRemote = None
42 42 try:
43 43 from vcsserver.hg import MercurialFactory, HgRemote
44 44 except ImportError:
45 45 MercurialFactory = None
46 46 HgRemote = None
47 47 try:
48 48 from vcsserver.svn import SubversionFactory, SvnRemote
49 49 except ImportError:
50 50 SubversionFactory = None
51 51 SvnRemote = None
52 52
53 53 log = logging.getLogger(__name__)
54 54
55 55
56 56 class VCS(object):
57 57 def __init__(self, locale=None, cache_config=None):
58 58 self.locale = locale
59 59 self.cache_config = cache_config
60 60 self._configure_locale()
61 61 self._initialize_cache()
62 62
63 63 if GitFactory and GitRemote:
64 64 git_repo_cache = self.cache.get_cache_region(
65 65 'git', region='repo_object')
66 66 git_factory = GitFactory(git_repo_cache)
67 67 self._git_remote = GitRemote(git_factory)
68 68 else:
69 69 log.info("Git client import failed")
70 70
71 71 if MercurialFactory and HgRemote:
72 72 hg_repo_cache = self.cache.get_cache_region(
73 73 'hg', region='repo_object')
74 74 hg_factory = MercurialFactory(hg_repo_cache)
75 75 self._hg_remote = HgRemote(hg_factory)
76 76 else:
77 77 log.info("Mercurial client import failed")
78 78
79 79 if SubversionFactory and SvnRemote:
80 80 svn_repo_cache = self.cache.get_cache_region(
81 81 'svn', region='repo_object')
82 82 svn_factory = SubversionFactory(svn_repo_cache)
83 83 self._svn_remote = SvnRemote(svn_factory, hg_factory=hg_factory)
84 84 else:
85 85 log.info("Subversion client import failed")
86 86
87 87 self._vcsserver = VcsServer()
88 88
89 89 def _initialize_cache(self):
90 90 cache_config = parse_cache_config_options(self.cache_config)
91 91 log.info('Initializing beaker cache: %s' % cache_config)
92 92 self.cache = CacheManager(**cache_config)
93 93
94 94 def _configure_locale(self):
95 95 if self.locale:
96 96 log.info('Settings locale: `LC_ALL` to %s' % self.locale)
97 97 else:
98 98 log.info(
99 99 'Configuring locale subsystem based on environment variables')
100 100 try:
101 101 # If self.locale is the empty string, then the locale
102 102 # module will use the environment variables. See the
103 103 # documentation of the package `locale`.
104 104 locale.setlocale(locale.LC_ALL, self.locale)
105 105
106 106 language_code, encoding = locale.getlocale()
107 107 log.info(
108 108 'Locale set to language code "%s" with encoding "%s".',
109 109 language_code, encoding)
110 110 except locale.Error:
111 111 log.exception(
112 112 'Cannot set locale, not configuring the locale system')
113 113
114 114
115 115 class WsgiProxy(object):
116 116 def __init__(self, wsgi):
117 117 self.wsgi = wsgi
118 118
119 119 def __call__(self, environ, start_response):
120 120 input_data = environ['wsgi.input'].read()
121 121 input_data = msgpack.unpackb(input_data)
122 122
123 123 error = None
124 124 try:
125 125 data, status, headers = self.wsgi.handle(
126 126 input_data['environment'], input_data['input_data'],
127 127 *input_data['args'], **input_data['kwargs'])
128 128 except Exception as e:
129 129 data, status, headers = [], None, None
130 130 error = {
131 131 'message': str(e),
132 132 '_vcs_kind': getattr(e, '_vcs_kind', None)
133 133 }
134 134
135 135 start_response(200, {})
136 136 return self._iterator(error, status, headers, data)
137 137
138 138 def _iterator(self, error, status, headers, data):
139 139 initial_data = [
140 140 error,
141 141 status,
142 142 headers,
143 143 ]
144 144
145 145 for d in chain(initial_data, data):
146 146 yield msgpack.packb(d)
147 147
148 148
149 149 class HTTPApplication(object):
150 150 ALLOWED_EXCEPTIONS = ('KeyError', 'URLError')
151 151
152 152 remote_wsgi = remote_wsgi
153 153 _use_echo_app = False
154 154
155 155 def __init__(self, settings=None):
156 156 self.config = Configurator(settings=settings)
157 157 locale = settings.get('', 'en_US.UTF-8')
158 158 vcs = VCS(locale=locale, cache_config=settings)
159 159 self._remotes = {
160 160 'hg': vcs._hg_remote,
161 161 'git': vcs._git_remote,
162 162 'svn': vcs._svn_remote,
163 163 'server': vcs._vcsserver,
164 164 }
165 165 if settings.get('dev.use_echo_app', 'false').lower() == 'true':
166 166 self._use_echo_app = True
167 167 log.warning("Using EchoApp for VCS operations.")
168 168 self.remote_wsgi = remote_wsgi_stub
169 169 self._configure_settings(settings)
170 170 self._configure()
171 171
172 172 def _configure_settings(self, app_settings):
173 173 """
174 174 Configure the settings module.
175 175 """
176 176 git_path = app_settings.get('git_path', None)
177 177 if git_path:
178 178 settings.GIT_EXECUTABLE = git_path
179 179
180 180 def _configure(self):
181 181 self.config.add_renderer(
182 182 name='msgpack',
183 183 factory=self._msgpack_renderer_factory)
184 184
185 self.config.add_route('service', '/_service')
185 186 self.config.add_route('status', '/status')
186 187 self.config.add_route('hg_proxy', '/proxy/hg')
187 188 self.config.add_route('git_proxy', '/proxy/git')
188 189 self.config.add_route('vcs', '/{backend}')
189 190 self.config.add_route('stream_git', '/stream/git/*repo_name')
190 191 self.config.add_route('stream_hg', '/stream/hg/*repo_name')
191 192
192 193 self.config.add_view(
193 194 self.status_view, route_name='status', renderer='json')
195 self.config.add_view(
196 self.service_view, route_name='service', renderer='msgpack')
197
194 198 self.config.add_view(self.hg_proxy(), route_name='hg_proxy')
195 199 self.config.add_view(self.git_proxy(), route_name='git_proxy')
196 200 self.config.add_view(
197 201 self.vcs_view, route_name='vcs', renderer='msgpack')
198 202
199 203 self.config.add_view(self.hg_stream(), route_name='stream_hg')
200 204 self.config.add_view(self.git_stream(), route_name='stream_git')
201 205 self.config.add_view(
202 206 self.handle_vcs_exception, context=Exception,
203 207 custom_predicates=[self.is_vcs_exception])
204 208
205 209 def wsgi_app(self):
206 210 return self.config.make_wsgi_app()
207 211
208 212 def vcs_view(self, request):
209 213 remote = self._remotes[request.matchdict['backend']]
210 214 payload = msgpack.unpackb(request.body, use_list=True)
211 215 method = payload.get('method')
212 216 params = payload.get('params')
213 217 wire = params.get('wire')
214 218 args = params.get('args')
215 219 kwargs = params.get('kwargs')
216 220 if wire:
217 221 try:
218 222 wire['context'] = uuid.UUID(wire['context'])
219 223 except KeyError:
220 224 pass
221 225 args.insert(0, wire)
222 226
223 227 try:
224 228 resp = getattr(remote, method)(*args, **kwargs)
225 229 except Exception as e:
226 230 type_ = e.__class__.__name__
227 231 if type_ not in self.ALLOWED_EXCEPTIONS:
228 232 type_ = None
229 233
230 234 resp = {
231 235 'id': payload.get('id'),
232 236 'error': {
233 237 'message': e.message,
234 238 'type': type_
235 239 }
236 240 }
237 241 try:
238 242 resp['error']['_vcs_kind'] = e._vcs_kind
239 243 except AttributeError:
240 244 pass
241 245 else:
242 246 resp = {
243 247 'id': payload.get('id'),
244 248 'result': resp
245 249 }
246 250
247 251 return resp
248 252
249 253 def status_view(self, request):
250 254 return {'status': 'OK'}
251 255
256 def service_view(self, request):
257 import vcsserver
258 payload = msgpack.unpackb(request.body, use_list=True)
259 resp = {
260 'id': payload.get('id'),
261 'result': dict(
262 version=vcsserver.__version__,
263 config={},
264 payload=payload,
265 )
266 }
267 return resp
268
252 269 def _msgpack_renderer_factory(self, info):
253 270 def _render(value, system):
254 271 value = msgpack.packb(value)
255 272 request = system.get('request')
256 273 if request is not None:
257 274 response = request.response
258 275 ct = response.content_type
259 276 if ct == response.default_content_type:
260 277 response.content_type = 'application/x-msgpack'
261 278 return value
262 279 return _render
263 280
264 281 def hg_proxy(self):
265 282 @wsgiapp
266 283 def _hg_proxy(environ, start_response):
267 284 app = WsgiProxy(self.remote_wsgi.HgRemoteWsgi())
268 285 return app(environ, start_response)
269 286 return _hg_proxy
270 287
271 288 def git_proxy(self):
272 289 @wsgiapp
273 290 def _git_proxy(environ, start_response):
274 291 app = WsgiProxy(self.remote_wsgi.GitRemoteWsgi())
275 292 return app(environ, start_response)
276 293 return _git_proxy
277 294
278 295 def hg_stream(self):
279 296 if self._use_echo_app:
280 297 @wsgiapp
281 298 def _hg_stream(environ, start_response):
282 299 app = EchoApp('fake_path', 'fake_name', None)
283 300 return app(environ, start_response)
284 301 return _hg_stream
285 302 else:
286 303 @wsgiapp
287 304 def _hg_stream(environ, start_response):
288 305 repo_path = environ['HTTP_X_RC_REPO_PATH']
289 306 repo_name = environ['HTTP_X_RC_REPO_NAME']
290 307 packed_config = base64.b64decode(
291 308 environ['HTTP_X_RC_REPO_CONFIG'])
292 309 config = msgpack.unpackb(packed_config)
293 310 app = scm_app.create_hg_wsgi_app(
294 311 repo_path, repo_name, config)
295 312
296 313 # Consitent path information for hgweb
297 314 environ['PATH_INFO'] = environ['HTTP_X_RC_PATH_INFO']
298 315 environ['REPO_NAME'] = repo_name
299 316 return app(environ, ResponseFilter(start_response))
300 317 return _hg_stream
301 318
302 319 def git_stream(self):
303 320 if self._use_echo_app:
304 321 @wsgiapp
305 322 def _git_stream(environ, start_response):
306 323 app = EchoApp('fake_path', 'fake_name', None)
307 324 return app(environ, start_response)
308 325 return _git_stream
309 326 else:
310 327 @wsgiapp
311 328 def _git_stream(environ, start_response):
312 329 repo_path = environ['HTTP_X_RC_REPO_PATH']
313 330 repo_name = environ['HTTP_X_RC_REPO_NAME']
314 331 packed_config = base64.b64decode(
315 332 environ['HTTP_X_RC_REPO_CONFIG'])
316 333 config = msgpack.unpackb(packed_config)
317 334
318 335 environ['PATH_INFO'] = environ['HTTP_X_RC_PATH_INFO']
319 336 app = scm_app.create_git_wsgi_app(
320 337 repo_path, repo_name, config)
321 338 return app(environ, start_response)
322 339 return _git_stream
323 340
324 341 def is_vcs_exception(self, context, request):
325 342 """
326 343 View predicate that returns true if the context object is a VCS
327 344 exception.
328 345 """
329 346 return hasattr(context, '_vcs_kind')
330 347
331 348 def handle_vcs_exception(self, exception, request):
332 349 if exception._vcs_kind == 'repo_locked':
333 350 # Get custom repo-locked status code if present.
334 351 status_code = request.headers.get('X-RC-Locked-Status-Code')
335 352 return HTTPRepoLocked(
336 353 title=exception.message, status_code=status_code)
337 354
338 355 # Re-raise exception if we can not handle it.
339 356 raise exception
340 357
341 358
342 359 class ResponseFilter(object):
343 360
344 361 def __init__(self, start_response):
345 362 self._start_response = start_response
346 363
347 364 def __call__(self, status, response_headers, exc_info=None):
348 365 headers = tuple(
349 366 (h, v) for h, v in response_headers
350 367 if not wsgiref.util.is_hop_by_hop(h))
351 368 return self._start_response(status, headers, exc_info)
352 369
353 370
354 371 def main(global_config, **settings):
355 372 if MercurialFactory:
356 373 hgpatches.patch_largefiles_capabilities()
357 374 hgpatches.patch_subrepo_type_mapping()
358 375 app = HTTPApplication(settings=settings)
359 376 return app.wsgi_app()
General Comments 0
You need to be logged in to leave comments. Login now