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