##// END OF EJS Templates
status: expose platform info usefull to debug docker LB issues
super-admin -
r1148:876a7843 default
parent child Browse files
Show More
@@ -1,768 +1,775 b''
1 1 # RhodeCode VCSServer provides access to different vcs backends via network.
2 2 # Copyright (C) 2014-2023 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 18 import io
19 19 import os
20 import platform
20 21 import sys
21 22 import locale
22 23 import logging
23 24 import uuid
24 25 import time
25 26 import wsgiref.util
26 27 import tempfile
27 28 import psutil
28 29
29 30 from itertools import chain
30 31
31 32 import msgpack
32 33 import configparser
33 34
34 35 from pyramid.config import Configurator
35 36 from pyramid.wsgi import wsgiapp
36 37 from pyramid.response import Response
37 38
38 39 from vcsserver.base import BytesEnvelope, BinaryEnvelope
39 40 from vcsserver.lib.rc_json import json
40 41 from vcsserver.config.settings_maker import SettingsMaker
41 42 from vcsserver.str_utils import safe_int
42 43 from vcsserver.lib.statsd_client import StatsdClient
43 44 from vcsserver.tweens.request_wrapper import get_headers_call_context
44 45
45 46 import vcsserver
46 47 from vcsserver import remote_wsgi, scm_app, settings, hgpatches
47 48 from vcsserver.git_lfs.app import GIT_LFS_CONTENT_TYPE, GIT_LFS_PROTO_PAT
48 49 from vcsserver.echo_stub import remote_wsgi as remote_wsgi_stub
49 50 from vcsserver.echo_stub.echo_app import EchoApp
50 51 from vcsserver.exceptions import HTTPRepoLocked, HTTPRepoBranchProtected
51 52 from vcsserver.lib.exc_tracking import store_exception, format_exc
52 53 from vcsserver.server import VcsServer
53 54
54 55 strict_vcs = True
55 56
56 57 git_import_err = None
57 58 try:
58 59 from vcsserver.remote.git_remote import GitFactory, GitRemote
59 60 except ImportError as e:
60 61 GitFactory = None
61 62 GitRemote = None
62 63 git_import_err = e
63 64 if strict_vcs:
64 65 raise
65 66
66 67
67 68 hg_import_err = None
68 69 try:
69 70 from vcsserver.remote.hg_remote import MercurialFactory, HgRemote
70 71 except ImportError as e:
71 72 MercurialFactory = None
72 73 HgRemote = None
73 74 hg_import_err = e
74 75 if strict_vcs:
75 76 raise
76 77
77 78
78 79 svn_import_err = None
79 80 try:
80 81 from vcsserver.remote.svn_remote import SubversionFactory, SvnRemote
81 82 except ImportError as e:
82 83 SubversionFactory = None
83 84 SvnRemote = None
84 85 svn_import_err = e
85 86 if strict_vcs:
86 87 raise
87 88
88 89 log = logging.getLogger(__name__)
89 90
90 91 # due to Mercurial/glibc2.27 problems we need to detect if locale settings are
91 92 # causing problems and "fix" it in case they do and fallback to LC_ALL = C
92 93
93 94 try:
94 95 locale.setlocale(locale.LC_ALL, '')
95 96 except locale.Error as e:
96 97 log.error(
97 98 'LOCALE ERROR: failed to set LC_ALL, fallback to LC_ALL=C, org error: %s', e)
98 99 os.environ['LC_ALL'] = 'C'
99 100
100 101
101 102 def _is_request_chunked(environ):
102 103 stream = environ.get('HTTP_TRANSFER_ENCODING', '') == 'chunked'
103 104 return stream
104 105
105 106
106 107 def log_max_fd():
107 108 try:
108 109 maxfd = psutil.Process().rlimit(psutil.RLIMIT_NOFILE)[1]
109 110 log.info('Max file descriptors value: %s', maxfd)
110 111 except Exception:
111 112 pass
112 113
113 114
114 115 class VCS(object):
115 116 def __init__(self, locale_conf=None, cache_config=None):
116 117 self.locale = locale_conf
117 118 self.cache_config = cache_config
118 119 self._configure_locale()
119 120
120 121 log_max_fd()
121 122
122 123 if GitFactory and GitRemote:
123 124 git_factory = GitFactory()
124 125 self._git_remote = GitRemote(git_factory)
125 126 else:
126 127 log.error("Git client import failed: %s", git_import_err)
127 128
128 129 if MercurialFactory and HgRemote:
129 130 hg_factory = MercurialFactory()
130 131 self._hg_remote = HgRemote(hg_factory)
131 132 else:
132 133 log.error("Mercurial client import failed: %s", hg_import_err)
133 134
134 135 if SubversionFactory and SvnRemote:
135 136 svn_factory = SubversionFactory()
136 137
137 138 # hg factory is used for svn url validation
138 139 hg_factory = MercurialFactory()
139 140 self._svn_remote = SvnRemote(svn_factory, hg_factory=hg_factory)
140 141 else:
141 142 log.error("Subversion client import failed: %s", svn_import_err)
142 143
143 144 self._vcsserver = VcsServer()
144 145
145 146 def _configure_locale(self):
146 147 if self.locale:
147 148 log.info('Settings locale: `LC_ALL` to %s', self.locale)
148 149 else:
149 150 log.info('Configuring locale subsystem based on environment variables')
150 151 try:
151 152 # If self.locale is the empty string, then the locale
152 153 # module will use the environment variables. See the
153 154 # documentation of the package `locale`.
154 155 locale.setlocale(locale.LC_ALL, self.locale)
155 156
156 157 language_code, encoding = locale.getlocale()
157 158 log.info(
158 159 'Locale set to language code "%s" with encoding "%s".',
159 160 language_code, encoding)
160 161 except locale.Error:
161 162 log.exception('Cannot set locale, not configuring the locale system')
162 163
163 164
164 165 class WsgiProxy(object):
165 166 def __init__(self, wsgi):
166 167 self.wsgi = wsgi
167 168
168 169 def __call__(self, environ, start_response):
169 170 input_data = environ['wsgi.input'].read()
170 171 input_data = msgpack.unpackb(input_data)
171 172
172 173 error = None
173 174 try:
174 175 data, status, headers = self.wsgi.handle(
175 176 input_data['environment'], input_data['input_data'],
176 177 *input_data['args'], **input_data['kwargs'])
177 178 except Exception as e:
178 179 data, status, headers = [], None, None
179 180 error = {
180 181 'message': str(e),
181 182 '_vcs_kind': getattr(e, '_vcs_kind', None)
182 183 }
183 184
184 185 start_response(200, {})
185 186 return self._iterator(error, status, headers, data)
186 187
187 188 def _iterator(self, error, status, headers, data):
188 189 initial_data = [
189 190 error,
190 191 status,
191 192 headers,
192 193 ]
193 194
194 195 for d in chain(initial_data, data):
195 196 yield msgpack.packb(d)
196 197
197 198
198 199 def not_found(request):
199 200 return {'status': '404 NOT FOUND'}
200 201
201 202
202 203 class VCSViewPredicate(object):
203 204 def __init__(self, val, config):
204 205 self.remotes = val
205 206
206 207 def text(self):
207 208 return f'vcs view method = {list(self.remotes.keys())}'
208 209
209 210 phash = text
210 211
211 212 def __call__(self, context, request):
212 213 """
213 214 View predicate that returns true if given backend is supported by
214 215 defined remotes.
215 216 """
216 217 backend = request.matchdict.get('backend')
217 218 return backend in self.remotes
218 219
219 220
220 221 class HTTPApplication(object):
221 222 ALLOWED_EXCEPTIONS = ('KeyError', 'URLError')
222 223
223 224 remote_wsgi = remote_wsgi
224 225 _use_echo_app = False
225 226
226 227 def __init__(self, settings=None, global_config=None):
227 228
228 229 self.config = Configurator(settings=settings)
229 230 # Init our statsd at very start
230 231 self.config.registry.statsd = StatsdClient.statsd
231 232 self.config.registry.vcs_call_context = {}
232 233
233 234 self.global_config = global_config
234 235 self.config.include('vcsserver.lib.rc_cache')
235 236 self.config.include('vcsserver.lib.rc_cache.archive_cache')
236 237
237 238 settings_locale = settings.get('locale', '') or 'en_US.UTF-8'
238 239 vcs = VCS(locale_conf=settings_locale, cache_config=settings)
239 240 self._remotes = {
240 241 'hg': vcs._hg_remote,
241 242 'git': vcs._git_remote,
242 243 'svn': vcs._svn_remote,
243 244 'server': vcs._vcsserver,
244 245 }
245 246 if settings.get('dev.use_echo_app', 'false').lower() == 'true':
246 247 self._use_echo_app = True
247 248 log.warning("Using EchoApp for VCS operations.")
248 249 self.remote_wsgi = remote_wsgi_stub
249 250
250 251 self._configure_settings(global_config, settings)
251 252
252 253 self._configure()
253 254
254 255 def _configure_settings(self, global_config, app_settings):
255 256 """
256 257 Configure the settings module.
257 258 """
258 259 settings_merged = global_config.copy()
259 260 settings_merged.update(app_settings)
260 261
261 262 git_path = app_settings.get('git_path', None)
262 263 if git_path:
263 264 settings.GIT_EXECUTABLE = git_path
264 265 binary_dir = app_settings.get('core.binary_dir', None)
265 266 if binary_dir:
266 267 settings.BINARY_DIR = binary_dir
267 268
268 269 # Store the settings to make them available to other modules.
269 270 vcsserver.PYRAMID_SETTINGS = settings_merged
270 271 vcsserver.CONFIG = settings_merged
271 272
272 273 def _configure(self):
273 274 self.config.add_renderer(name='msgpack', factory=self._msgpack_renderer_factory)
274 275
275 276 self.config.add_route('service', '/_service')
276 277 self.config.add_route('status', '/status')
277 278 self.config.add_route('hg_proxy', '/proxy/hg')
278 279 self.config.add_route('git_proxy', '/proxy/git')
279 280
280 281 # rpc methods
281 282 self.config.add_route('vcs', '/{backend}')
282 283
283 284 # streaming rpc remote methods
284 285 self.config.add_route('vcs_stream', '/{backend}/stream')
285 286
286 287 # vcs operations clone/push as streaming
287 288 self.config.add_route('stream_git', '/stream/git/*repo_name')
288 289 self.config.add_route('stream_hg', '/stream/hg/*repo_name')
289 290
290 291 self.config.add_view(self.status_view, route_name='status', renderer='json')
291 292 self.config.add_view(self.service_view, route_name='service', renderer='msgpack')
292 293
293 294 self.config.add_view(self.hg_proxy(), route_name='hg_proxy')
294 295 self.config.add_view(self.git_proxy(), route_name='git_proxy')
295 296 self.config.add_view(self.vcs_view, route_name='vcs', renderer='msgpack',
296 297 vcs_view=self._remotes)
297 298 self.config.add_view(self.vcs_stream_view, route_name='vcs_stream',
298 299 vcs_view=self._remotes)
299 300
300 301 self.config.add_view(self.hg_stream(), route_name='stream_hg')
301 302 self.config.add_view(self.git_stream(), route_name='stream_git')
302 303
303 304 self.config.add_view_predicate('vcs_view', VCSViewPredicate)
304 305
305 306 self.config.add_notfound_view(not_found, renderer='json')
306 307
307 308 self.config.add_view(self.handle_vcs_exception, context=Exception)
308 309
309 310 self.config.add_tween(
310 311 'vcsserver.tweens.request_wrapper.RequestWrapperTween',
311 312 )
312 313 self.config.add_request_method(
313 314 'vcsserver.lib.request_counter.get_request_counter',
314 315 'request_count')
315 316
316 317 def wsgi_app(self):
317 318 return self.config.make_wsgi_app()
318 319
319 320 def _vcs_view_params(self, request):
320 321 remote = self._remotes[request.matchdict['backend']]
321 322 payload = msgpack.unpackb(request.body, use_list=True)
322 323
323 324 method = payload.get('method')
324 325 params = payload['params']
325 326 wire = params.get('wire')
326 327 args = params.get('args')
327 328 kwargs = params.get('kwargs')
328 329 context_uid = None
329 330
330 331 request.registry.vcs_call_context = {
331 332 'method': method,
332 333 'repo_name': payload.get('_repo_name'),
333 334 }
334 335
335 336 if wire:
336 337 try:
337 338 wire['context'] = context_uid = uuid.UUID(wire['context'])
338 339 except KeyError:
339 340 pass
340 341 args.insert(0, wire)
341 342 repo_state_uid = wire.get('repo_state_uid') if wire else None
342 343
343 344 # NOTE(marcink): trading complexity for slight performance
344 345 if log.isEnabledFor(logging.DEBUG):
345 346 # also we SKIP printing out any of those methods args since they maybe excessive
346 347 just_args_methods = {
347 348 'commitctx': ('content', 'removed', 'updated'),
348 349 'commit': ('content', 'removed', 'updated')
349 350 }
350 351 if method in just_args_methods:
351 352 skip_args = just_args_methods[method]
352 353 call_args = ''
353 354 call_kwargs = {}
354 355 for k in kwargs:
355 356 if k in skip_args:
356 357 # replace our skip key with dummy
357 358 call_kwargs[k] = f'RemovedParam({k})'
358 359 else:
359 360 call_kwargs[k] = kwargs[k]
360 361 else:
361 362 call_args = args[1:]
362 363 call_kwargs = kwargs
363 364
364 365 log.debug('Method requested:`%s` with args:%s kwargs:%s context_uid: %s, repo_state_uid:%s',
365 366 method, call_args, call_kwargs, context_uid, repo_state_uid)
366 367
367 368 statsd = request.registry.statsd
368 369 if statsd:
369 370 statsd.incr(
370 371 'vcsserver_method_total', tags=[
371 372 f"method:{method}",
372 373 ])
373 374 return payload, remote, method, args, kwargs
374 375
375 376 def vcs_view(self, request):
376 377
377 378 payload, remote, method, args, kwargs = self._vcs_view_params(request)
378 379 payload_id = payload.get('id')
379 380
380 381 try:
381 382 resp = getattr(remote, method)(*args, **kwargs)
382 383 except Exception as e:
383 384 exc_info = list(sys.exc_info())
384 385 exc_type, exc_value, exc_traceback = exc_info
385 386
386 387 org_exc = getattr(e, '_org_exc', None)
387 388 org_exc_name = None
388 389 org_exc_tb = ''
389 390 if org_exc:
390 391 org_exc_name = org_exc.__class__.__name__
391 392 org_exc_tb = getattr(e, '_org_exc_tb', '')
392 393 # replace our "faked" exception with our org
393 394 exc_info[0] = org_exc.__class__
394 395 exc_info[1] = org_exc
395 396
396 397 should_store_exc = True
397 398 if org_exc:
398 399 def get_exc_fqn(_exc_obj):
399 400 module_name = getattr(org_exc.__class__, '__module__', 'UNKNOWN')
400 401 return module_name + '.' + org_exc_name
401 402
402 403 exc_fqn = get_exc_fqn(org_exc)
403 404
404 405 if exc_fqn in ['mercurial.error.RepoLookupError',
405 406 'vcsserver.exceptions.RefNotFoundException']:
406 407 should_store_exc = False
407 408
408 409 if should_store_exc:
409 410 store_exception(id(exc_info), exc_info, request_path=request.path)
410 411
411 412 tb_info = format_exc(exc_info)
412 413
413 414 type_ = e.__class__.__name__
414 415 if type_ not in self.ALLOWED_EXCEPTIONS:
415 416 type_ = None
416 417
417 418 resp = {
418 419 'id': payload_id,
419 420 'error': {
420 421 'message': str(e),
421 422 'traceback': tb_info,
422 423 'org_exc': org_exc_name,
423 424 'org_exc_tb': org_exc_tb,
424 425 'type': type_
425 426 }
426 427 }
427 428
428 429 try:
429 430 resp['error']['_vcs_kind'] = getattr(e, '_vcs_kind', None)
430 431 except AttributeError:
431 432 pass
432 433 else:
433 434 resp = {
434 435 'id': payload_id,
435 436 'result': resp
436 437 }
437 438 log.debug('Serving data for method %s', method)
438 439 return resp
439 440
440 441 def vcs_stream_view(self, request):
441 442 payload, remote, method, args, kwargs = self._vcs_view_params(request)
442 443 # this method has a stream: marker we remove it here
443 444 method = method.split('stream:')[-1]
444 445 chunk_size = safe_int(payload.get('chunk_size')) or 4096
445 446
446 447 resp = getattr(remote, method)(*args, **kwargs)
447 448
448 449 def get_chunked_data(method_resp):
449 450 stream = io.BytesIO(method_resp)
450 451 while 1:
451 452 chunk = stream.read(chunk_size)
452 453 if not chunk:
453 454 break
454 455 yield chunk
455 456
456 457 response = Response(app_iter=get_chunked_data(resp))
457 458 response.content_type = 'application/octet-stream'
458 459
459 460 return response
460 461
461 462 def status_view(self, request):
462 463 import vcsserver
463 return {'status': 'OK', 'vcsserver_version': vcsserver.__version__,
464 'pid': os.getpid()}
464 _platform_id = platform.uname()[1] or 'instance'
465
466 return {
467 "status": "OK",
468 "vcsserver_version": vcsserver.__version__,
469 "platform": _platform_id,
470 "pid": os.getpid(),
471 }
465 472
466 473 def service_view(self, request):
467 474 import vcsserver
468 475
469 476 payload = msgpack.unpackb(request.body, use_list=True)
470 477 server_config, app_config = {}, {}
471 478
472 479 try:
473 480 path = self.global_config['__file__']
474 481 config = configparser.RawConfigParser()
475 482
476 483 config.read(path)
477 484
478 485 if config.has_section('server:main'):
479 486 server_config = dict(config.items('server:main'))
480 487 if config.has_section('app:main'):
481 488 app_config = dict(config.items('app:main'))
482 489
483 490 except Exception:
484 491 log.exception('Failed to read .ini file for display')
485 492
486 493 environ = list(os.environ.items())
487 494
488 495 resp = {
489 496 'id': payload.get('id'),
490 497 'result': dict(
491 498 version=vcsserver.__version__,
492 499 config=server_config,
493 500 app_config=app_config,
494 501 environ=environ,
495 502 payload=payload,
496 503 )
497 504 }
498 505 return resp
499 506
500 507 def _msgpack_renderer_factory(self, info):
501 508
502 509 def _render(value, system):
503 510 bin_type = False
504 511 res = value.get('result')
505 512 if isinstance(res, BytesEnvelope):
506 513 log.debug('Result is wrapped in BytesEnvelope type')
507 514 bin_type = True
508 515 elif isinstance(res, BinaryEnvelope):
509 516 log.debug('Result is wrapped in BinaryEnvelope type')
510 517 value['result'] = res.val
511 518 bin_type = True
512 519
513 520 request = system.get('request')
514 521 if request is not None:
515 522 response = request.response
516 523 ct = response.content_type
517 524 if ct == response.default_content_type:
518 525 response.content_type = 'application/x-msgpack'
519 526 if bin_type:
520 527 response.content_type = 'application/x-msgpack-bin'
521 528
522 529 return msgpack.packb(value, use_bin_type=bin_type)
523 530 return _render
524 531
525 532 def set_env_from_config(self, environ, config):
526 533 dict_conf = {}
527 534 try:
528 535 for elem in config:
529 536 if elem[0] == 'rhodecode':
530 537 dict_conf = json.loads(elem[2])
531 538 break
532 539 except Exception:
533 540 log.exception('Failed to fetch SCM CONFIG')
534 541 return
535 542
536 543 username = dict_conf.get('username')
537 544 if username:
538 545 environ['REMOTE_USER'] = username
539 546 # mercurial specific, some extension api rely on this
540 547 environ['HGUSER'] = username
541 548
542 549 ip = dict_conf.get('ip')
543 550 if ip:
544 551 environ['REMOTE_HOST'] = ip
545 552
546 553 if _is_request_chunked(environ):
547 554 # set the compatibility flag for webob
548 555 environ['wsgi.input_terminated'] = True
549 556
550 557 def hg_proxy(self):
551 558 @wsgiapp
552 559 def _hg_proxy(environ, start_response):
553 560 app = WsgiProxy(self.remote_wsgi.HgRemoteWsgi())
554 561 return app(environ, start_response)
555 562 return _hg_proxy
556 563
557 564 def git_proxy(self):
558 565 @wsgiapp
559 566 def _git_proxy(environ, start_response):
560 567 app = WsgiProxy(self.remote_wsgi.GitRemoteWsgi())
561 568 return app(environ, start_response)
562 569 return _git_proxy
563 570
564 571 def hg_stream(self):
565 572 if self._use_echo_app:
566 573 @wsgiapp
567 574 def _hg_stream(environ, start_response):
568 575 app = EchoApp('fake_path', 'fake_name', None)
569 576 return app(environ, start_response)
570 577 return _hg_stream
571 578 else:
572 579 @wsgiapp
573 580 def _hg_stream(environ, start_response):
574 581 log.debug('http-app: handling hg stream')
575 582 call_context = get_headers_call_context(environ)
576 583
577 584 repo_path = call_context['repo_path']
578 585 repo_name = call_context['repo_name']
579 586 config = call_context['repo_config']
580 587
581 588 app = scm_app.create_hg_wsgi_app(
582 589 repo_path, repo_name, config)
583 590
584 591 # Consistent path information for hgweb
585 592 environ['PATH_INFO'] = call_context['path_info']
586 593 environ['REPO_NAME'] = repo_name
587 594 self.set_env_from_config(environ, config)
588 595
589 596 log.debug('http-app: starting app handler '
590 597 'with %s and process request', app)
591 598 return app(environ, ResponseFilter(start_response))
592 599 return _hg_stream
593 600
594 601 def git_stream(self):
595 602 if self._use_echo_app:
596 603 @wsgiapp
597 604 def _git_stream(environ, start_response):
598 605 app = EchoApp('fake_path', 'fake_name', None)
599 606 return app(environ, start_response)
600 607 return _git_stream
601 608 else:
602 609 @wsgiapp
603 610 def _git_stream(environ, start_response):
604 611 log.debug('http-app: handling git stream')
605 612
606 613 call_context = get_headers_call_context(environ)
607 614
608 615 repo_path = call_context['repo_path']
609 616 repo_name = call_context['repo_name']
610 617 config = call_context['repo_config']
611 618
612 619 environ['PATH_INFO'] = call_context['path_info']
613 620 self.set_env_from_config(environ, config)
614 621
615 622 content_type = environ.get('CONTENT_TYPE', '')
616 623
617 624 path = environ['PATH_INFO']
618 625 is_lfs_request = GIT_LFS_CONTENT_TYPE in content_type
619 626 log.debug(
620 627 'LFS: Detecting if request `%s` is LFS server path based '
621 628 'on content type:`%s`, is_lfs:%s',
622 629 path, content_type, is_lfs_request)
623 630
624 631 if not is_lfs_request:
625 632 # fallback detection by path
626 633 if GIT_LFS_PROTO_PAT.match(path):
627 634 is_lfs_request = True
628 635 log.debug(
629 636 'LFS: fallback detection by path of: `%s`, is_lfs:%s',
630 637 path, is_lfs_request)
631 638
632 639 if is_lfs_request:
633 640 app = scm_app.create_git_lfs_wsgi_app(
634 641 repo_path, repo_name, config)
635 642 else:
636 643 app = scm_app.create_git_wsgi_app(
637 644 repo_path, repo_name, config)
638 645
639 646 log.debug('http-app: starting app handler '
640 647 'with %s and process request', app)
641 648
642 649 return app(environ, start_response)
643 650
644 651 return _git_stream
645 652
646 653 def handle_vcs_exception(self, exception, request):
647 654 _vcs_kind = getattr(exception, '_vcs_kind', '')
648 655
649 656 if _vcs_kind == 'repo_locked':
650 657 headers_call_context = get_headers_call_context(request.environ)
651 658 status_code = safe_int(headers_call_context['locked_status_code'])
652 659
653 660 return HTTPRepoLocked(
654 661 title=str(exception), status_code=status_code, headers=[('X-Rc-Locked', '1')])
655 662
656 663 elif _vcs_kind == 'repo_branch_protected':
657 664 # Get custom repo-branch-protected status code if present.
658 665 return HTTPRepoBranchProtected(
659 666 title=str(exception), headers=[('X-Rc-Branch-Protection', '1')])
660 667
661 668 exc_info = request.exc_info
662 669 store_exception(id(exc_info), exc_info)
663 670
664 671 traceback_info = 'unavailable'
665 672 if request.exc_info:
666 673 traceback_info = format_exc(request.exc_info)
667 674
668 675 log.error(
669 676 'error occurred handling this request for path: %s, \n%s',
670 677 request.path, traceback_info)
671 678
672 679 statsd = request.registry.statsd
673 680 if statsd:
674 681 exc_type = f"{exception.__class__.__module__}.{exception.__class__.__name__}"
675 682 statsd.incr('vcsserver_exception_total',
676 683 tags=[f"type:{exc_type}"])
677 684 raise exception
678 685
679 686
680 687 class ResponseFilter(object):
681 688
682 689 def __init__(self, start_response):
683 690 self._start_response = start_response
684 691
685 692 def __call__(self, status, response_headers, exc_info=None):
686 693 headers = tuple(
687 694 (h, v) for h, v in response_headers
688 695 if not wsgiref.util.is_hop_by_hop(h))
689 696 return self._start_response(status, headers, exc_info)
690 697
691 698
692 699 def sanitize_settings_and_apply_defaults(global_config, settings):
693 700 _global_settings_maker = SettingsMaker(global_config)
694 701 settings_maker = SettingsMaker(settings)
695 702
696 703 settings_maker.make_setting('logging.autoconfigure', False, parser='bool')
697 704
698 705 logging_conf = os.path.join(os.path.dirname(global_config.get('__file__')), 'logging.ini')
699 706 settings_maker.enable_logging(logging_conf)
700 707
701 708 # Default includes, possible to change as a user
702 709 pyramid_includes = settings_maker.make_setting('pyramid.includes', [], parser='list:newline')
703 710 log.debug("Using the following pyramid.includes: %s", pyramid_includes)
704 711
705 712 settings_maker.make_setting('__file__', global_config.get('__file__'))
706 713
707 714 settings_maker.make_setting('pyramid.default_locale_name', 'en')
708 715 settings_maker.make_setting('locale', 'en_US.UTF-8')
709 716
710 717 settings_maker.make_setting('core.binary_dir', '')
711 718
712 719 temp_store = tempfile.gettempdir()
713 720 default_cache_dir = os.path.join(temp_store, 'rc_cache')
714 721 # save default, cache dir, and use it for all backends later.
715 722 default_cache_dir = settings_maker.make_setting(
716 723 'cache_dir',
717 724 default=default_cache_dir, default_when_empty=True,
718 725 parser='dir:ensured')
719 726
720 727 # exception store cache
721 728 settings_maker.make_setting(
722 729 'exception_tracker.store_path',
723 730 default=os.path.join(default_cache_dir, 'exc_store'), default_when_empty=True,
724 731 parser='dir:ensured'
725 732 )
726 733
727 734 # repo_object cache defaults
728 735 settings_maker.make_setting(
729 736 'rc_cache.repo_object.backend',
730 737 default='dogpile.cache.rc.file_namespace',
731 738 parser='string')
732 739 settings_maker.make_setting(
733 740 'rc_cache.repo_object.expiration_time',
734 741 default=30 * 24 * 60 * 60, # 30days
735 742 parser='int')
736 743 settings_maker.make_setting(
737 744 'rc_cache.repo_object.arguments.filename',
738 745 default=os.path.join(default_cache_dir, 'vcsserver_cache_repo_object.db'),
739 746 parser='string')
740 747
741 748 # statsd
742 749 settings_maker.make_setting('statsd.enabled', False, parser='bool')
743 750 settings_maker.make_setting('statsd.statsd_host', 'statsd-exporter', parser='string')
744 751 settings_maker.make_setting('statsd.statsd_port', 9125, parser='int')
745 752 settings_maker.make_setting('statsd.statsd_prefix', '')
746 753 settings_maker.make_setting('statsd.statsd_ipv6', False, parser='bool')
747 754
748 755 settings_maker.env_expand()
749 756
750 757
751 758 def main(global_config, **settings):
752 759 start_time = time.time()
753 760 log.info('Pyramid app config starting')
754 761
755 762 if MercurialFactory:
756 763 hgpatches.patch_largefiles_capabilities()
757 764 hgpatches.patch_subrepo_type_mapping()
758 765
759 766 # Fill in and sanitize the defaults & do ENV expansion
760 767 sanitize_settings_and_apply_defaults(global_config, settings)
761 768
762 769 # init and bootstrap StatsdClient
763 770 StatsdClient.setup(settings)
764 771
765 772 pyramid_app = HTTPApplication(settings=settings, global_config=global_config).wsgi_app()
766 773 total_time = time.time() - start_time
767 774 log.info('Pyramid app created and configured in %.2fs', total_time)
768 775 return pyramid_app
General Comments 0
You need to be logged in to leave comments. Login now