##// END OF EJS Templates
logs: added some added logging for stream hg/git.
marcink -
r247:73f3558e default
parent child Browse files
Show More
@@ -1,434 +1,441 b''
1 1 # RhodeCode VCSServer provides access to different vcs backends via network.
2 2 # Copyright (C) 2014-2017 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 import traceback
24 24 from itertools import chain
25 25
26 26 import msgpack
27 27 from beaker.cache import CacheManager
28 28 from beaker.util import parse_cache_config_options
29 29 from pyramid.config import Configurator
30 30 from pyramid.wsgi import wsgiapp
31 31
32 32 from vcsserver import remote_wsgi, scm_app, settings, hgpatches
33 33 from vcsserver.git_lfs.app import GIT_LFS_CONTENT_TYPE, GIT_LFS_PROTO_PAT
34 34 from vcsserver.echo_stub import remote_wsgi as remote_wsgi_stub
35 35 from vcsserver.echo_stub.echo_app import EchoApp
36 36 from vcsserver.exceptions import HTTPRepoLocked
37 37 from vcsserver.server import VcsServer
38 38
39 39 try:
40 40 from vcsserver.git import GitFactory, GitRemote
41 41 except ImportError:
42 42 GitFactory = None
43 43 GitRemote = None
44 44
45 45 try:
46 46 from vcsserver.hg import MercurialFactory, HgRemote
47 47 except ImportError:
48 48 MercurialFactory = None
49 49 HgRemote = None
50 50
51 51 try:
52 52 from vcsserver.svn import SubversionFactory, SvnRemote
53 53 except ImportError:
54 54 SubversionFactory = None
55 55 SvnRemote = None
56 56
57 57 log = logging.getLogger(__name__)
58 58
59 59
60 60 class VCS(object):
61 61 def __init__(self, locale=None, cache_config=None):
62 62 self.locale = locale
63 63 self.cache_config = cache_config
64 64 self._configure_locale()
65 65 self._initialize_cache()
66 66
67 67 if GitFactory and GitRemote:
68 68 git_repo_cache = self.cache.get_cache_region(
69 69 'git', region='repo_object')
70 70 git_factory = GitFactory(git_repo_cache)
71 71 self._git_remote = GitRemote(git_factory)
72 72 else:
73 73 log.info("Git client import failed")
74 74
75 75 if MercurialFactory and HgRemote:
76 76 hg_repo_cache = self.cache.get_cache_region(
77 77 'hg', region='repo_object')
78 78 hg_factory = MercurialFactory(hg_repo_cache)
79 79 self._hg_remote = HgRemote(hg_factory)
80 80 else:
81 81 log.info("Mercurial client import failed")
82 82
83 83 if SubversionFactory and SvnRemote:
84 84 svn_repo_cache = self.cache.get_cache_region(
85 85 'svn', region='repo_object')
86 86 svn_factory = SubversionFactory(svn_repo_cache)
87 87 self._svn_remote = SvnRemote(svn_factory, hg_factory=hg_factory)
88 88 else:
89 89 log.info("Subversion client import failed")
90 90
91 91 self._vcsserver = VcsServer()
92 92
93 93 def _initialize_cache(self):
94 94 cache_config = parse_cache_config_options(self.cache_config)
95 95 log.info('Initializing beaker cache: %s' % cache_config)
96 96 self.cache = CacheManager(**cache_config)
97 97
98 98 def _configure_locale(self):
99 99 if self.locale:
100 100 log.info('Settings locale: `LC_ALL` to %s' % self.locale)
101 101 else:
102 102 log.info(
103 103 'Configuring locale subsystem based on environment variables')
104 104 try:
105 105 # If self.locale is the empty string, then the locale
106 106 # module will use the environment variables. See the
107 107 # documentation of the package `locale`.
108 108 locale.setlocale(locale.LC_ALL, self.locale)
109 109
110 110 language_code, encoding = locale.getlocale()
111 111 log.info(
112 112 'Locale set to language code "%s" with encoding "%s".',
113 113 language_code, encoding)
114 114 except locale.Error:
115 115 log.exception(
116 116 'Cannot set locale, not configuring the locale system')
117 117
118 118
119 119 class WsgiProxy(object):
120 120 def __init__(self, wsgi):
121 121 self.wsgi = wsgi
122 122
123 123 def __call__(self, environ, start_response):
124 124 input_data = environ['wsgi.input'].read()
125 125 input_data = msgpack.unpackb(input_data)
126 126
127 127 error = None
128 128 try:
129 129 data, status, headers = self.wsgi.handle(
130 130 input_data['environment'], input_data['input_data'],
131 131 *input_data['args'], **input_data['kwargs'])
132 132 except Exception as e:
133 133 data, status, headers = [], None, None
134 134 error = {
135 135 'message': str(e),
136 136 '_vcs_kind': getattr(e, '_vcs_kind', None)
137 137 }
138 138
139 139 start_response(200, {})
140 140 return self._iterator(error, status, headers, data)
141 141
142 142 def _iterator(self, error, status, headers, data):
143 143 initial_data = [
144 144 error,
145 145 status,
146 146 headers,
147 147 ]
148 148
149 149 for d in chain(initial_data, data):
150 150 yield msgpack.packb(d)
151 151
152 152
153 153 class HTTPApplication(object):
154 154 ALLOWED_EXCEPTIONS = ('KeyError', 'URLError')
155 155
156 156 remote_wsgi = remote_wsgi
157 157 _use_echo_app = False
158 158
159 159 def __init__(self, settings=None, global_config=None):
160 160 self.config = Configurator(settings=settings)
161 161 self.global_config = global_config
162 162
163 163 locale = settings.get('locale', '') or 'en_US.UTF-8'
164 164 vcs = VCS(locale=locale, cache_config=settings)
165 165 self._remotes = {
166 166 'hg': vcs._hg_remote,
167 167 'git': vcs._git_remote,
168 168 'svn': vcs._svn_remote,
169 169 'server': vcs._vcsserver,
170 170 }
171 171 if settings.get('dev.use_echo_app', 'false').lower() == 'true':
172 172 self._use_echo_app = True
173 173 log.warning("Using EchoApp for VCS operations.")
174 174 self.remote_wsgi = remote_wsgi_stub
175 175 self._configure_settings(settings)
176 176 self._configure()
177 177
178 178 def _configure_settings(self, app_settings):
179 179 """
180 180 Configure the settings module.
181 181 """
182 182 git_path = app_settings.get('git_path', None)
183 183 if git_path:
184 184 settings.GIT_EXECUTABLE = git_path
185 185
186 186 def _configure(self):
187 187 self.config.add_renderer(
188 188 name='msgpack',
189 189 factory=self._msgpack_renderer_factory)
190 190
191 191 self.config.add_route('service', '/_service')
192 192 self.config.add_route('status', '/status')
193 193 self.config.add_route('hg_proxy', '/proxy/hg')
194 194 self.config.add_route('git_proxy', '/proxy/git')
195 195 self.config.add_route('vcs', '/{backend}')
196 196 self.config.add_route('stream_git', '/stream/git/*repo_name')
197 197 self.config.add_route('stream_hg', '/stream/hg/*repo_name')
198 198
199 199 self.config.add_view(
200 200 self.status_view, route_name='status', renderer='json')
201 201 self.config.add_view(
202 202 self.service_view, route_name='service', renderer='msgpack')
203 203
204 204 self.config.add_view(self.hg_proxy(), route_name='hg_proxy')
205 205 self.config.add_view(self.git_proxy(), route_name='git_proxy')
206 206 self.config.add_view(
207 207 self.vcs_view, route_name='vcs', renderer='msgpack',
208 208 custom_predicates=[self.is_vcs_view])
209 209
210 210 self.config.add_view(self.hg_stream(), route_name='stream_hg')
211 211 self.config.add_view(self.git_stream(), route_name='stream_git')
212 212
213 213 def notfound(request):
214 214 return {'status': '404 NOT FOUND'}
215 215 self.config.add_notfound_view(notfound, renderer='json')
216 216
217 217 self.config.add_view(self.handle_vcs_exception, context=Exception)
218 218
219 219 self.config.add_tween(
220 220 'vcsserver.tweens.RequestWrapperTween',
221 221 )
222 222
223 223 def wsgi_app(self):
224 224 return self.config.make_wsgi_app()
225 225
226 226 def vcs_view(self, request):
227 227 remote = self._remotes[request.matchdict['backend']]
228 228 payload = msgpack.unpackb(request.body, use_list=True)
229 229 method = payload.get('method')
230 230 params = payload.get('params')
231 231 wire = params.get('wire')
232 232 args = params.get('args')
233 233 kwargs = params.get('kwargs')
234 234 if wire:
235 235 try:
236 236 wire['context'] = uuid.UUID(wire['context'])
237 237 except KeyError:
238 238 pass
239 239 args.insert(0, wire)
240 240
241 241 log.debug('method called:%s with kwargs:%s', method, kwargs)
242 242 try:
243 243 resp = getattr(remote, method)(*args, **kwargs)
244 244 except Exception as e:
245 245 tb_info = traceback.format_exc()
246 246
247 247 type_ = e.__class__.__name__
248 248 if type_ not in self.ALLOWED_EXCEPTIONS:
249 249 type_ = None
250 250
251 251 resp = {
252 252 'id': payload.get('id'),
253 253 'error': {
254 254 'message': e.message,
255 255 'traceback': tb_info,
256 256 'type': type_
257 257 }
258 258 }
259 259 try:
260 260 resp['error']['_vcs_kind'] = e._vcs_kind
261 261 except AttributeError:
262 262 pass
263 263 else:
264 264 resp = {
265 265 'id': payload.get('id'),
266 266 'result': resp
267 267 }
268 268
269 269 return resp
270 270
271 271 def status_view(self, request):
272 272 return {'status': 'OK'}
273 273
274 274 def service_view(self, request):
275 275 import vcsserver
276 276 import ConfigParser as configparser
277 277
278 278 payload = msgpack.unpackb(request.body, use_list=True)
279 279
280 280 try:
281 281 path = self.global_config['__file__']
282 282 config = configparser.ConfigParser()
283 283 config.read(path)
284 284 parsed_ini = config
285 285 if parsed_ini.has_section('server:main'):
286 286 parsed_ini = dict(parsed_ini.items('server:main'))
287 287 except Exception:
288 288 log.exception('Failed to read .ini file for display')
289 289 parsed_ini = {}
290 290
291 291 resp = {
292 292 'id': payload.get('id'),
293 293 'result': dict(
294 294 version=vcsserver.__version__,
295 295 config=parsed_ini,
296 296 payload=payload,
297 297 )
298 298 }
299 299 return resp
300 300
301 301 def _msgpack_renderer_factory(self, info):
302 302 def _render(value, system):
303 303 value = msgpack.packb(value)
304 304 request = system.get('request')
305 305 if request is not None:
306 306 response = request.response
307 307 ct = response.content_type
308 308 if ct == response.default_content_type:
309 309 response.content_type = 'application/x-msgpack'
310 310 return value
311 311 return _render
312 312
313 313 def hg_proxy(self):
314 314 @wsgiapp
315 315 def _hg_proxy(environ, start_response):
316 316 app = WsgiProxy(self.remote_wsgi.HgRemoteWsgi())
317 317 return app(environ, start_response)
318 318 return _hg_proxy
319 319
320 320 def git_proxy(self):
321 321 @wsgiapp
322 322 def _git_proxy(environ, start_response):
323 323 app = WsgiProxy(self.remote_wsgi.GitRemoteWsgi())
324 324 return app(environ, start_response)
325 325 return _git_proxy
326 326
327 327 def hg_stream(self):
328 328 if self._use_echo_app:
329 329 @wsgiapp
330 330 def _hg_stream(environ, start_response):
331 331 app = EchoApp('fake_path', 'fake_name', None)
332 332 return app(environ, start_response)
333 333 return _hg_stream
334 334 else:
335 335 @wsgiapp
336 336 def _hg_stream(environ, start_response):
337 log.debug('http-app: handling hg stream')
337 338 repo_path = environ['HTTP_X_RC_REPO_PATH']
338 339 repo_name = environ['HTTP_X_RC_REPO_NAME']
339 340 packed_config = base64.b64decode(
340 341 environ['HTTP_X_RC_REPO_CONFIG'])
341 342 config = msgpack.unpackb(packed_config)
342 343 app = scm_app.create_hg_wsgi_app(
343 344 repo_path, repo_name, config)
344 345
345 346 # Consitent path information for hgweb
346 347 environ['PATH_INFO'] = environ['HTTP_X_RC_PATH_INFO']
347 348 environ['REPO_NAME'] = repo_name
349 log.debug('http-app: starting app handler '
350 'with %s and process request', app)
348 351 return app(environ, ResponseFilter(start_response))
349 352 return _hg_stream
350 353
351 354 def git_stream(self):
352 355 if self._use_echo_app:
353 356 @wsgiapp
354 357 def _git_stream(environ, start_response):
355 358 app = EchoApp('fake_path', 'fake_name', None)
356 359 return app(environ, start_response)
357 360 return _git_stream
358 361 else:
359 362 @wsgiapp
360 363 def _git_stream(environ, start_response):
364 log.debug('http-app: handling git stream')
361 365 repo_path = environ['HTTP_X_RC_REPO_PATH']
362 366 repo_name = environ['HTTP_X_RC_REPO_NAME']
363 367 packed_config = base64.b64decode(
364 368 environ['HTTP_X_RC_REPO_CONFIG'])
365 369 config = msgpack.unpackb(packed_config)
366 370
367 371 environ['PATH_INFO'] = environ['HTTP_X_RC_PATH_INFO']
368 372 content_type = environ.get('CONTENT_TYPE', '')
369 373
370 374 path = environ['PATH_INFO']
371 375 is_lfs_request = GIT_LFS_CONTENT_TYPE in content_type
372 376 log.debug(
373 377 'LFS: Detecting if request `%s` is LFS server path based '
374 378 'on content type:`%s`, is_lfs:%s',
375 379 path, content_type, is_lfs_request)
376 380
377 381 if not is_lfs_request:
378 382 # fallback detection by path
379 383 if GIT_LFS_PROTO_PAT.match(path):
380 384 is_lfs_request = True
381 385 log.debug(
382 386 'LFS: fallback detection by path of: `%s`, is_lfs:%s',
383 387 path, is_lfs_request)
384 388
385 389 if is_lfs_request:
386 390 app = scm_app.create_git_lfs_wsgi_app(
387 391 repo_path, repo_name, config)
388 392 else:
389 393 app = scm_app.create_git_wsgi_app(
390 394 repo_path, repo_name, config)
395
396 log.debug('http-app: starting app handler '
397 'with %s and process request', app)
391 398 return app(environ, start_response)
392 399
393 400 return _git_stream
394 401
395 402 def is_vcs_view(self, context, request):
396 403 """
397 404 View predicate that returns true if given backend is supported by
398 405 defined remotes.
399 406 """
400 407 backend = request.matchdict.get('backend')
401 408 return backend in self._remotes
402 409
403 410 def handle_vcs_exception(self, exception, request):
404 411 _vcs_kind = getattr(exception, '_vcs_kind', '')
405 412 if _vcs_kind == 'repo_locked':
406 413 # Get custom repo-locked status code if present.
407 414 status_code = request.headers.get('X-RC-Locked-Status-Code')
408 415 return HTTPRepoLocked(
409 416 title=exception.message, status_code=status_code)
410 417
411 418 # Re-raise exception if we can not handle it.
412 419 log.exception(
413 420 'error occurred handling this request for path: %s', request.path)
414 421 raise exception
415 422
416 423
417 424 class ResponseFilter(object):
418 425
419 426 def __init__(self, start_response):
420 427 self._start_response = start_response
421 428
422 429 def __call__(self, status, response_headers, exc_info=None):
423 430 headers = tuple(
424 431 (h, v) for h, v in response_headers
425 432 if not wsgiref.util.is_hop_by_hop(h))
426 433 return self._start_response(status, headers, exc_info)
427 434
428 435
429 436 def main(global_config, **settings):
430 437 if MercurialFactory:
431 438 hgpatches.patch_largefiles_capabilities()
432 439 hgpatches.patch_subrepo_type_mapping()
433 440 app = HTTPApplication(settings=settings, global_config=global_config)
434 441 return app.wsgi_app()
General Comments 0
You need to be logged in to leave comments. Login now