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