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