##// END OF EJS Templates
logging: use lazy formatting of log entries
marcink -
r541:3e5cbdba default
parent child Browse files
Show More
@@ -1,598 +1,598 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-2018 RhodeCode GmbH
2 # Copyright (C) 2014-2018 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 wsgiref.util
24 import wsgiref.util
25 import traceback
25 import traceback
26 import tempfile
26 import tempfile
27 from itertools import chain
27 from itertools import chain
28
28
29 import simplejson as json
29 import simplejson as json
30 import msgpack
30 import msgpack
31 from pyramid.config import Configurator
31 from pyramid.config import Configurator
32 from pyramid.settings import asbool, aslist
32 from pyramid.settings import asbool, aslist
33 from pyramid.wsgi import wsgiapp
33 from pyramid.wsgi import wsgiapp
34 from pyramid.compat import configparser
34 from pyramid.compat import configparser
35
35
36
36
37 log = logging.getLogger(__name__)
37 log = logging.getLogger(__name__)
38
38
39 # due to Mercurial/glibc2.27 problems we need to detect if locale settings are
39 # due to Mercurial/glibc2.27 problems we need to detect if locale settings are
40 # causing problems and "fix" it in case they do and fallback to LC_ALL = C
40 # causing problems and "fix" it in case they do and fallback to LC_ALL = C
41
41
42 try:
42 try:
43 locale.setlocale(locale.LC_ALL, '')
43 locale.setlocale(locale.LC_ALL, '')
44 except locale.Error as e:
44 except locale.Error as e:
45 log.error(
45 log.error(
46 'LOCALE ERROR: failed to set LC_ALL, fallback to LC_ALL=C, org error: %s', e)
46 'LOCALE ERROR: failed to set LC_ALL, fallback to LC_ALL=C, org error: %s', e)
47 os.environ['LC_ALL'] = 'C'
47 os.environ['LC_ALL'] = 'C'
48
48
49 import vcsserver
49 import vcsserver
50 from vcsserver import remote_wsgi, scm_app, settings, hgpatches
50 from vcsserver import remote_wsgi, scm_app, settings, hgpatches
51 from vcsserver.git_lfs.app import GIT_LFS_CONTENT_TYPE, GIT_LFS_PROTO_PAT
51 from vcsserver.git_lfs.app import GIT_LFS_CONTENT_TYPE, GIT_LFS_PROTO_PAT
52 from vcsserver.echo_stub import remote_wsgi as remote_wsgi_stub
52 from vcsserver.echo_stub import remote_wsgi as remote_wsgi_stub
53 from vcsserver.echo_stub.echo_app import EchoApp
53 from vcsserver.echo_stub.echo_app import EchoApp
54 from vcsserver.exceptions import HTTPRepoLocked, HTTPRepoBranchProtected
54 from vcsserver.exceptions import HTTPRepoLocked, HTTPRepoBranchProtected
55 from vcsserver.lib.exc_tracking import store_exception
55 from vcsserver.lib.exc_tracking import store_exception
56 from vcsserver.server import VcsServer
56 from vcsserver.server import VcsServer
57
57
58 try:
58 try:
59 from vcsserver.git import GitFactory, GitRemote
59 from vcsserver.git import GitFactory, GitRemote
60 except ImportError:
60 except ImportError:
61 GitFactory = None
61 GitFactory = None
62 GitRemote = None
62 GitRemote = None
63
63
64 try:
64 try:
65 from vcsserver.hg import MercurialFactory, HgRemote
65 from vcsserver.hg import MercurialFactory, HgRemote
66 except ImportError:
66 except ImportError:
67 MercurialFactory = None
67 MercurialFactory = None
68 HgRemote = None
68 HgRemote = None
69
69
70 try:
70 try:
71 from vcsserver.svn import SubversionFactory, SvnRemote
71 from vcsserver.svn import SubversionFactory, SvnRemote
72 except ImportError:
72 except ImportError:
73 SubversionFactory = None
73 SubversionFactory = None
74 SvnRemote = None
74 SvnRemote = None
75
75
76
76
77 def _is_request_chunked(environ):
77 def _is_request_chunked(environ):
78 stream = environ.get('HTTP_TRANSFER_ENCODING', '') == 'chunked'
78 stream = environ.get('HTTP_TRANSFER_ENCODING', '') == 'chunked'
79 return stream
79 return stream
80
80
81
81
82 def _int_setting(settings, name, default):
82 def _int_setting(settings, name, default):
83 settings[name] = int(settings.get(name, default))
83 settings[name] = int(settings.get(name, default))
84 return settings[name]
84 return settings[name]
85
85
86
86
87 def _bool_setting(settings, name, default):
87 def _bool_setting(settings, name, default):
88 input_val = settings.get(name, default)
88 input_val = settings.get(name, default)
89 if isinstance(input_val, unicode):
89 if isinstance(input_val, unicode):
90 input_val = input_val.encode('utf8')
90 input_val = input_val.encode('utf8')
91 settings[name] = asbool(input_val)
91 settings[name] = asbool(input_val)
92 return settings[name]
92 return settings[name]
93
93
94
94
95 def _list_setting(settings, name, default):
95 def _list_setting(settings, name, default):
96 raw_value = settings.get(name, default)
96 raw_value = settings.get(name, default)
97
97
98 # Otherwise we assume it uses pyramids space/newline separation.
98 # Otherwise we assume it uses pyramids space/newline separation.
99 settings[name] = aslist(raw_value)
99 settings[name] = aslist(raw_value)
100 return settings[name]
100 return settings[name]
101
101
102
102
103 def _string_setting(settings, name, default, lower=True, default_when_empty=False):
103 def _string_setting(settings, name, default, lower=True, default_when_empty=False):
104 value = settings.get(name, default)
104 value = settings.get(name, default)
105
105
106 if default_when_empty and not value:
106 if default_when_empty and not value:
107 # use default value when value is empty
107 # use default value when value is empty
108 value = default
108 value = default
109
109
110 if lower:
110 if lower:
111 value = value.lower()
111 value = value.lower()
112 settings[name] = value
112 settings[name] = value
113 return settings[name]
113 return settings[name]
114
114
115
115
116 class VCS(object):
116 class VCS(object):
117 def __init__(self, locale=None, cache_config=None):
117 def __init__(self, locale=None, cache_config=None):
118 self.locale = locale
118 self.locale = locale
119 self.cache_config = cache_config
119 self.cache_config = cache_config
120 self._configure_locale()
120 self._configure_locale()
121
121
122 if GitFactory and GitRemote:
122 if GitFactory and GitRemote:
123 git_factory = GitFactory()
123 git_factory = GitFactory()
124 self._git_remote = GitRemote(git_factory)
124 self._git_remote = GitRemote(git_factory)
125 else:
125 else:
126 log.info("Git client import failed")
126 log.info("Git client import failed")
127
127
128 if MercurialFactory and HgRemote:
128 if MercurialFactory and HgRemote:
129 hg_factory = MercurialFactory()
129 hg_factory = MercurialFactory()
130 self._hg_remote = HgRemote(hg_factory)
130 self._hg_remote = HgRemote(hg_factory)
131 else:
131 else:
132 log.info("Mercurial client import failed")
132 log.info("Mercurial client import failed")
133
133
134 if SubversionFactory and SvnRemote:
134 if SubversionFactory and SvnRemote:
135 svn_factory = SubversionFactory()
135 svn_factory = SubversionFactory()
136
136
137 # hg factory is used for svn url validation
137 # hg factory is used for svn url validation
138 hg_factory = MercurialFactory()
138 hg_factory = MercurialFactory()
139 self._svn_remote = SvnRemote(svn_factory, hg_factory=hg_factory)
139 self._svn_remote = SvnRemote(svn_factory, hg_factory=hg_factory)
140 else:
140 else:
141 log.info("Subversion client import failed")
141 log.info("Subversion client import failed")
142
142
143 self._vcsserver = VcsServer()
143 self._vcsserver = VcsServer()
144
144
145 def _configure_locale(self):
145 def _configure_locale(self):
146 if self.locale:
146 if self.locale:
147 log.info('Settings locale: `LC_ALL` to %s' % self.locale)
147 log.info('Settings locale: `LC_ALL` to %s', self.locale)
148 else:
148 else:
149 log.info(
149 log.info(
150 'Configuring locale subsystem based on environment variables')
150 '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(
162 log.exception(
163 'Cannot set locale, not configuring the locale system')
163 'Cannot set locale, not configuring the locale system')
164
164
165
165
166 class WsgiProxy(object):
166 class WsgiProxy(object):
167 def __init__(self, wsgi):
167 def __init__(self, wsgi):
168 self.wsgi = wsgi
168 self.wsgi = wsgi
169
169
170 def __call__(self, environ, start_response):
170 def __call__(self, environ, start_response):
171 input_data = environ['wsgi.input'].read()
171 input_data = environ['wsgi.input'].read()
172 input_data = msgpack.unpackb(input_data)
172 input_data = msgpack.unpackb(input_data)
173
173
174 error = None
174 error = None
175 try:
175 try:
176 data, status, headers = self.wsgi.handle(
176 data, status, headers = self.wsgi.handle(
177 input_data['environment'], input_data['input_data'],
177 input_data['environment'], input_data['input_data'],
178 *input_data['args'], **input_data['kwargs'])
178 *input_data['args'], **input_data['kwargs'])
179 except Exception as e:
179 except Exception as e:
180 data, status, headers = [], None, None
180 data, status, headers = [], None, None
181 error = {
181 error = {
182 'message': str(e),
182 'message': str(e),
183 '_vcs_kind': getattr(e, '_vcs_kind', None)
183 '_vcs_kind': getattr(e, '_vcs_kind', None)
184 }
184 }
185
185
186 start_response(200, {})
186 start_response(200, {})
187 return self._iterator(error, status, headers, data)
187 return self._iterator(error, status, headers, data)
188
188
189 def _iterator(self, error, status, headers, data):
189 def _iterator(self, error, status, headers, data):
190 initial_data = [
190 initial_data = [
191 error,
191 error,
192 status,
192 status,
193 headers,
193 headers,
194 ]
194 ]
195
195
196 for d in chain(initial_data, data):
196 for d in chain(initial_data, data):
197 yield msgpack.packb(d)
197 yield msgpack.packb(d)
198
198
199
199
200 class HTTPApplication(object):
200 class HTTPApplication(object):
201 ALLOWED_EXCEPTIONS = ('KeyError', 'URLError')
201 ALLOWED_EXCEPTIONS = ('KeyError', 'URLError')
202
202
203 remote_wsgi = remote_wsgi
203 remote_wsgi = remote_wsgi
204 _use_echo_app = False
204 _use_echo_app = False
205
205
206 def __init__(self, settings=None, global_config=None):
206 def __init__(self, settings=None, global_config=None):
207 self._sanitize_settings_and_apply_defaults(settings)
207 self._sanitize_settings_and_apply_defaults(settings)
208
208
209 self.config = Configurator(settings=settings)
209 self.config = Configurator(settings=settings)
210 self.global_config = global_config
210 self.global_config = global_config
211 self.config.include('vcsserver.lib.rc_cache')
211 self.config.include('vcsserver.lib.rc_cache')
212
212
213 locale = settings.get('locale', '') or 'en_US.UTF-8'
213 locale = settings.get('locale', '') or 'en_US.UTF-8'
214 vcs = VCS(locale=locale, cache_config=settings)
214 vcs = VCS(locale=locale, cache_config=settings)
215 self._remotes = {
215 self._remotes = {
216 'hg': vcs._hg_remote,
216 'hg': vcs._hg_remote,
217 'git': vcs._git_remote,
217 'git': vcs._git_remote,
218 'svn': vcs._svn_remote,
218 'svn': vcs._svn_remote,
219 'server': vcs._vcsserver,
219 'server': vcs._vcsserver,
220 }
220 }
221 if settings.get('dev.use_echo_app', 'false').lower() == 'true':
221 if settings.get('dev.use_echo_app', 'false').lower() == 'true':
222 self._use_echo_app = True
222 self._use_echo_app = True
223 log.warning("Using EchoApp for VCS operations.")
223 log.warning("Using EchoApp for VCS operations.")
224 self.remote_wsgi = remote_wsgi_stub
224 self.remote_wsgi = remote_wsgi_stub
225
225
226 self._configure_settings(global_config, settings)
226 self._configure_settings(global_config, settings)
227 self._configure()
227 self._configure()
228
228
229 def _configure_settings(self, global_config, app_settings):
229 def _configure_settings(self, global_config, app_settings):
230 """
230 """
231 Configure the settings module.
231 Configure the settings module.
232 """
232 """
233 settings_merged = global_config.copy()
233 settings_merged = global_config.copy()
234 settings_merged.update(app_settings)
234 settings_merged.update(app_settings)
235
235
236 git_path = app_settings.get('git_path', None)
236 git_path = app_settings.get('git_path', None)
237 if git_path:
237 if git_path:
238 settings.GIT_EXECUTABLE = git_path
238 settings.GIT_EXECUTABLE = git_path
239 binary_dir = app_settings.get('core.binary_dir', None)
239 binary_dir = app_settings.get('core.binary_dir', None)
240 if binary_dir:
240 if binary_dir:
241 settings.BINARY_DIR = binary_dir
241 settings.BINARY_DIR = binary_dir
242
242
243 # Store the settings to make them available to other modules.
243 # Store the settings to make them available to other modules.
244 vcsserver.PYRAMID_SETTINGS = settings_merged
244 vcsserver.PYRAMID_SETTINGS = settings_merged
245 vcsserver.CONFIG = settings_merged
245 vcsserver.CONFIG = settings_merged
246
246
247 def _sanitize_settings_and_apply_defaults(self, settings):
247 def _sanitize_settings_and_apply_defaults(self, settings):
248
248
249 default_cache_dir = os.path.join(tempfile.gettempdir(), 'rc_cache')
249 default_cache_dir = os.path.join(tempfile.gettempdir(), 'rc_cache')
250
250
251 # save default, cache dir, and use it for all backends later.
251 # save default, cache dir, and use it for all backends later.
252 default_cache_dir = _string_setting(
252 default_cache_dir = _string_setting(
253 settings,
253 settings,
254 'cache_dir',
254 'cache_dir',
255 default_cache_dir, lower=False, default_when_empty=True)
255 default_cache_dir, lower=False, default_when_empty=True)
256
256
257 # ensure we have our dir created
257 # ensure we have our dir created
258 if not os.path.isdir(default_cache_dir):
258 if not os.path.isdir(default_cache_dir):
259 os.makedirs(default_cache_dir, mode=0755)
259 os.makedirs(default_cache_dir, mode=0755)
260
260
261 # exception store cache
261 # exception store cache
262 _string_setting(
262 _string_setting(
263 settings,
263 settings,
264 'exception_tracker.store_path',
264 'exception_tracker.store_path',
265 default_cache_dir, lower=False)
265 default_cache_dir, lower=False)
266
266
267 # repo_object cache
267 # repo_object cache
268 _string_setting(
268 _string_setting(
269 settings,
269 settings,
270 'rc_cache.repo_object.backend',
270 'rc_cache.repo_object.backend',
271 'dogpile.cache.rc.memory_lru')
271 'dogpile.cache.rc.memory_lru')
272 _int_setting(
272 _int_setting(
273 settings,
273 settings,
274 'rc_cache.repo_object.expiration_time',
274 'rc_cache.repo_object.expiration_time',
275 300)
275 300)
276 _int_setting(
276 _int_setting(
277 settings,
277 settings,
278 'rc_cache.repo_object.max_size',
278 'rc_cache.repo_object.max_size',
279 1024)
279 1024)
280
280
281 def _configure(self):
281 def _configure(self):
282 self.config.add_renderer(
282 self.config.add_renderer(
283 name='msgpack',
283 name='msgpack',
284 factory=self._msgpack_renderer_factory)
284 factory=self._msgpack_renderer_factory)
285
285
286 self.config.add_route('service', '/_service')
286 self.config.add_route('service', '/_service')
287 self.config.add_route('status', '/status')
287 self.config.add_route('status', '/status')
288 self.config.add_route('hg_proxy', '/proxy/hg')
288 self.config.add_route('hg_proxy', '/proxy/hg')
289 self.config.add_route('git_proxy', '/proxy/git')
289 self.config.add_route('git_proxy', '/proxy/git')
290 self.config.add_route('vcs', '/{backend}')
290 self.config.add_route('vcs', '/{backend}')
291 self.config.add_route('stream_git', '/stream/git/*repo_name')
291 self.config.add_route('stream_git', '/stream/git/*repo_name')
292 self.config.add_route('stream_hg', '/stream/hg/*repo_name')
292 self.config.add_route('stream_hg', '/stream/hg/*repo_name')
293
293
294 self.config.add_view(
294 self.config.add_view(
295 self.status_view, route_name='status', renderer='json')
295 self.status_view, route_name='status', renderer='json')
296 self.config.add_view(
296 self.config.add_view(
297 self.service_view, route_name='service', renderer='msgpack')
297 self.service_view, route_name='service', renderer='msgpack')
298
298
299 self.config.add_view(self.hg_proxy(), route_name='hg_proxy')
299 self.config.add_view(self.hg_proxy(), route_name='hg_proxy')
300 self.config.add_view(self.git_proxy(), route_name='git_proxy')
300 self.config.add_view(self.git_proxy(), route_name='git_proxy')
301 self.config.add_view(
301 self.config.add_view(
302 self.vcs_view, route_name='vcs', renderer='msgpack',
302 self.vcs_view, route_name='vcs', renderer='msgpack',
303 custom_predicates=[self.is_vcs_view])
303 custom_predicates=[self.is_vcs_view])
304
304
305 self.config.add_view(self.hg_stream(), route_name='stream_hg')
305 self.config.add_view(self.hg_stream(), route_name='stream_hg')
306 self.config.add_view(self.git_stream(), route_name='stream_git')
306 self.config.add_view(self.git_stream(), route_name='stream_git')
307
307
308 def notfound(request):
308 def notfound(request):
309 return {'status': '404 NOT FOUND'}
309 return {'status': '404 NOT FOUND'}
310 self.config.add_notfound_view(notfound, renderer='json')
310 self.config.add_notfound_view(notfound, renderer='json')
311
311
312 self.config.add_view(self.handle_vcs_exception, context=Exception)
312 self.config.add_view(self.handle_vcs_exception, context=Exception)
313
313
314 self.config.add_tween(
314 self.config.add_tween(
315 'vcsserver.tweens.RequestWrapperTween',
315 'vcsserver.tweens.RequestWrapperTween',
316 )
316 )
317
317
318 def wsgi_app(self):
318 def wsgi_app(self):
319 return self.config.make_wsgi_app()
319 return self.config.make_wsgi_app()
320
320
321 def vcs_view(self, request):
321 def vcs_view(self, request):
322 remote = self._remotes[request.matchdict['backend']]
322 remote = self._remotes[request.matchdict['backend']]
323 payload = msgpack.unpackb(request.body, use_list=True)
323 payload = msgpack.unpackb(request.body, use_list=True)
324 method = payload.get('method')
324 method = payload.get('method')
325 params = payload.get('params')
325 params = payload.get('params')
326 wire = params.get('wire')
326 wire = params.get('wire')
327 args = params.get('args')
327 args = params.get('args')
328 kwargs = params.get('kwargs')
328 kwargs = params.get('kwargs')
329 context_uid = None
329 context_uid = None
330
330
331 if wire:
331 if wire:
332 try:
332 try:
333 wire['context'] = context_uid = uuid.UUID(wire['context'])
333 wire['context'] = context_uid = uuid.UUID(wire['context'])
334 except KeyError:
334 except KeyError:
335 pass
335 pass
336 args.insert(0, wire)
336 args.insert(0, wire)
337
337
338 log.debug('method called:%s with kwargs:%s context_uid: %s',
338 log.debug('method called:%s with kwargs:%s context_uid: %s',
339 method, kwargs, context_uid)
339 method, kwargs, context_uid)
340 try:
340 try:
341 resp = getattr(remote, method)(*args, **kwargs)
341 resp = getattr(remote, method)(*args, **kwargs)
342 except Exception as e:
342 except Exception as e:
343 exc_info = list(sys.exc_info())
343 exc_info = list(sys.exc_info())
344 exc_type, exc_value, exc_traceback = exc_info
344 exc_type, exc_value, exc_traceback = exc_info
345
345
346 org_exc = getattr(e, '_org_exc', None)
346 org_exc = getattr(e, '_org_exc', None)
347 org_exc_name = None
347 org_exc_name = None
348 if org_exc:
348 if org_exc:
349 org_exc_name = org_exc.__class__.__name__
349 org_exc_name = org_exc.__class__.__name__
350 # replace our "faked" exception with our org
350 # replace our "faked" exception with our org
351 exc_info[0] = org_exc.__class__
351 exc_info[0] = org_exc.__class__
352 exc_info[1] = org_exc
352 exc_info[1] = org_exc
353
353
354 store_exception(id(exc_info), exc_info)
354 store_exception(id(exc_info), exc_info)
355
355
356 tb_info = ''.join(
356 tb_info = ''.join(
357 traceback.format_exception(exc_type, exc_value, exc_traceback))
357 traceback.format_exception(exc_type, exc_value, exc_traceback))
358
358
359 type_ = e.__class__.__name__
359 type_ = e.__class__.__name__
360 if type_ not in self.ALLOWED_EXCEPTIONS:
360 if type_ not in self.ALLOWED_EXCEPTIONS:
361 type_ = None
361 type_ = None
362
362
363 resp = {
363 resp = {
364 'id': payload.get('id'),
364 'id': payload.get('id'),
365 'error': {
365 'error': {
366 'message': e.message,
366 'message': e.message,
367 'traceback': tb_info,
367 'traceback': tb_info,
368 'org_exc': org_exc_name,
368 'org_exc': org_exc_name,
369 'type': type_
369 'type': type_
370 }
370 }
371 }
371 }
372 try:
372 try:
373 resp['error']['_vcs_kind'] = getattr(e, '_vcs_kind', None)
373 resp['error']['_vcs_kind'] = getattr(e, '_vcs_kind', None)
374 except AttributeError:
374 except AttributeError:
375 pass
375 pass
376 else:
376 else:
377 resp = {
377 resp = {
378 'id': payload.get('id'),
378 'id': payload.get('id'),
379 'result': resp
379 'result': resp
380 }
380 }
381
381
382 return resp
382 return resp
383
383
384 def status_view(self, request):
384 def status_view(self, request):
385 import vcsserver
385 import vcsserver
386 return {'status': 'OK', 'vcsserver_version': vcsserver.__version__,
386 return {'status': 'OK', 'vcsserver_version': vcsserver.__version__,
387 'pid': os.getpid()}
387 'pid': os.getpid()}
388
388
389 def service_view(self, request):
389 def service_view(self, request):
390 import vcsserver
390 import vcsserver
391
391
392 payload = msgpack.unpackb(request.body, use_list=True)
392 payload = msgpack.unpackb(request.body, use_list=True)
393
393
394 try:
394 try:
395 path = self.global_config['__file__']
395 path = self.global_config['__file__']
396 config = configparser.ConfigParser()
396 config = configparser.ConfigParser()
397 config.read(path)
397 config.read(path)
398 parsed_ini = config
398 parsed_ini = config
399 if parsed_ini.has_section('server:main'):
399 if parsed_ini.has_section('server:main'):
400 parsed_ini = dict(parsed_ini.items('server:main'))
400 parsed_ini = dict(parsed_ini.items('server:main'))
401 except Exception:
401 except Exception:
402 log.exception('Failed to read .ini file for display')
402 log.exception('Failed to read .ini file for display')
403 parsed_ini = {}
403 parsed_ini = {}
404
404
405 resp = {
405 resp = {
406 'id': payload.get('id'),
406 'id': payload.get('id'),
407 'result': dict(
407 'result': dict(
408 version=vcsserver.__version__,
408 version=vcsserver.__version__,
409 config=parsed_ini,
409 config=parsed_ini,
410 payload=payload,
410 payload=payload,
411 )
411 )
412 }
412 }
413 return resp
413 return resp
414
414
415 def _msgpack_renderer_factory(self, info):
415 def _msgpack_renderer_factory(self, info):
416 def _render(value, system):
416 def _render(value, system):
417 value = msgpack.packb(value)
417 value = msgpack.packb(value)
418 request = system.get('request')
418 request = system.get('request')
419 if request is not None:
419 if request is not None:
420 response = request.response
420 response = request.response
421 ct = response.content_type
421 ct = response.content_type
422 if ct == response.default_content_type:
422 if ct == response.default_content_type:
423 response.content_type = 'application/x-msgpack'
423 response.content_type = 'application/x-msgpack'
424 return value
424 return value
425 return _render
425 return _render
426
426
427 def set_env_from_config(self, environ, config):
427 def set_env_from_config(self, environ, config):
428 dict_conf = {}
428 dict_conf = {}
429 try:
429 try:
430 for elem in config:
430 for elem in config:
431 if elem[0] == 'rhodecode':
431 if elem[0] == 'rhodecode':
432 dict_conf = json.loads(elem[2])
432 dict_conf = json.loads(elem[2])
433 break
433 break
434 except Exception:
434 except Exception:
435 log.exception('Failed to fetch SCM CONFIG')
435 log.exception('Failed to fetch SCM CONFIG')
436 return
436 return
437
437
438 username = dict_conf.get('username')
438 username = dict_conf.get('username')
439 if username:
439 if username:
440 environ['REMOTE_USER'] = username
440 environ['REMOTE_USER'] = username
441 # mercurial specific, some extension api rely on this
441 # mercurial specific, some extension api rely on this
442 environ['HGUSER'] = username
442 environ['HGUSER'] = username
443
443
444 ip = dict_conf.get('ip')
444 ip = dict_conf.get('ip')
445 if ip:
445 if ip:
446 environ['REMOTE_HOST'] = ip
446 environ['REMOTE_HOST'] = ip
447
447
448 if _is_request_chunked(environ):
448 if _is_request_chunked(environ):
449 # set the compatibility flag for webob
449 # set the compatibility flag for webob
450 environ['wsgi.input_terminated'] = True
450 environ['wsgi.input_terminated'] = True
451
451
452 def hg_proxy(self):
452 def hg_proxy(self):
453 @wsgiapp
453 @wsgiapp
454 def _hg_proxy(environ, start_response):
454 def _hg_proxy(environ, start_response):
455 app = WsgiProxy(self.remote_wsgi.HgRemoteWsgi())
455 app = WsgiProxy(self.remote_wsgi.HgRemoteWsgi())
456 return app(environ, start_response)
456 return app(environ, start_response)
457 return _hg_proxy
457 return _hg_proxy
458
458
459 def git_proxy(self):
459 def git_proxy(self):
460 @wsgiapp
460 @wsgiapp
461 def _git_proxy(environ, start_response):
461 def _git_proxy(environ, start_response):
462 app = WsgiProxy(self.remote_wsgi.GitRemoteWsgi())
462 app = WsgiProxy(self.remote_wsgi.GitRemoteWsgi())
463 return app(environ, start_response)
463 return app(environ, start_response)
464 return _git_proxy
464 return _git_proxy
465
465
466 def hg_stream(self):
466 def hg_stream(self):
467 if self._use_echo_app:
467 if self._use_echo_app:
468 @wsgiapp
468 @wsgiapp
469 def _hg_stream(environ, start_response):
469 def _hg_stream(environ, start_response):
470 app = EchoApp('fake_path', 'fake_name', None)
470 app = EchoApp('fake_path', 'fake_name', None)
471 return app(environ, start_response)
471 return app(environ, start_response)
472 return _hg_stream
472 return _hg_stream
473 else:
473 else:
474 @wsgiapp
474 @wsgiapp
475 def _hg_stream(environ, start_response):
475 def _hg_stream(environ, start_response):
476 log.debug('http-app: handling hg stream')
476 log.debug('http-app: handling hg stream')
477 repo_path = environ['HTTP_X_RC_REPO_PATH']
477 repo_path = environ['HTTP_X_RC_REPO_PATH']
478 repo_name = environ['HTTP_X_RC_REPO_NAME']
478 repo_name = environ['HTTP_X_RC_REPO_NAME']
479 packed_config = base64.b64decode(
479 packed_config = base64.b64decode(
480 environ['HTTP_X_RC_REPO_CONFIG'])
480 environ['HTTP_X_RC_REPO_CONFIG'])
481 config = msgpack.unpackb(packed_config)
481 config = msgpack.unpackb(packed_config)
482 app = scm_app.create_hg_wsgi_app(
482 app = scm_app.create_hg_wsgi_app(
483 repo_path, repo_name, config)
483 repo_path, repo_name, config)
484
484
485 # Consistent path information for hgweb
485 # Consistent path information for hgweb
486 environ['PATH_INFO'] = environ['HTTP_X_RC_PATH_INFO']
486 environ['PATH_INFO'] = environ['HTTP_X_RC_PATH_INFO']
487 environ['REPO_NAME'] = repo_name
487 environ['REPO_NAME'] = repo_name
488 self.set_env_from_config(environ, config)
488 self.set_env_from_config(environ, config)
489
489
490 log.debug('http-app: starting app handler '
490 log.debug('http-app: starting app handler '
491 'with %s and process request', app)
491 'with %s and process request', app)
492 return app(environ, ResponseFilter(start_response))
492 return app(environ, ResponseFilter(start_response))
493 return _hg_stream
493 return _hg_stream
494
494
495 def git_stream(self):
495 def git_stream(self):
496 if self._use_echo_app:
496 if self._use_echo_app:
497 @wsgiapp
497 @wsgiapp
498 def _git_stream(environ, start_response):
498 def _git_stream(environ, start_response):
499 app = EchoApp('fake_path', 'fake_name', None)
499 app = EchoApp('fake_path', 'fake_name', None)
500 return app(environ, start_response)
500 return app(environ, start_response)
501 return _git_stream
501 return _git_stream
502 else:
502 else:
503 @wsgiapp
503 @wsgiapp
504 def _git_stream(environ, start_response):
504 def _git_stream(environ, start_response):
505 log.debug('http-app: handling git stream')
505 log.debug('http-app: handling git stream')
506 repo_path = environ['HTTP_X_RC_REPO_PATH']
506 repo_path = environ['HTTP_X_RC_REPO_PATH']
507 repo_name = environ['HTTP_X_RC_REPO_NAME']
507 repo_name = environ['HTTP_X_RC_REPO_NAME']
508 packed_config = base64.b64decode(
508 packed_config = base64.b64decode(
509 environ['HTTP_X_RC_REPO_CONFIG'])
509 environ['HTTP_X_RC_REPO_CONFIG'])
510 config = msgpack.unpackb(packed_config)
510 config = msgpack.unpackb(packed_config)
511
511
512 environ['PATH_INFO'] = environ['HTTP_X_RC_PATH_INFO']
512 environ['PATH_INFO'] = environ['HTTP_X_RC_PATH_INFO']
513 self.set_env_from_config(environ, config)
513 self.set_env_from_config(environ, config)
514
514
515 content_type = environ.get('CONTENT_TYPE', '')
515 content_type = environ.get('CONTENT_TYPE', '')
516
516
517 path = environ['PATH_INFO']
517 path = environ['PATH_INFO']
518 is_lfs_request = GIT_LFS_CONTENT_TYPE in content_type
518 is_lfs_request = GIT_LFS_CONTENT_TYPE in content_type
519 log.debug(
519 log.debug(
520 'LFS: Detecting if request `%s` is LFS server path based '
520 'LFS: Detecting if request `%s` is LFS server path based '
521 'on content type:`%s`, is_lfs:%s',
521 'on content type:`%s`, is_lfs:%s',
522 path, content_type, is_lfs_request)
522 path, content_type, is_lfs_request)
523
523
524 if not is_lfs_request:
524 if not is_lfs_request:
525 # fallback detection by path
525 # fallback detection by path
526 if GIT_LFS_PROTO_PAT.match(path):
526 if GIT_LFS_PROTO_PAT.match(path):
527 is_lfs_request = True
527 is_lfs_request = True
528 log.debug(
528 log.debug(
529 'LFS: fallback detection by path of: `%s`, is_lfs:%s',
529 'LFS: fallback detection by path of: `%s`, is_lfs:%s',
530 path, is_lfs_request)
530 path, is_lfs_request)
531
531
532 if is_lfs_request:
532 if is_lfs_request:
533 app = scm_app.create_git_lfs_wsgi_app(
533 app = scm_app.create_git_lfs_wsgi_app(
534 repo_path, repo_name, config)
534 repo_path, repo_name, config)
535 else:
535 else:
536 app = scm_app.create_git_wsgi_app(
536 app = scm_app.create_git_wsgi_app(
537 repo_path, repo_name, config)
537 repo_path, repo_name, config)
538
538
539 log.debug('http-app: starting app handler '
539 log.debug('http-app: starting app handler '
540 'with %s and process request', app)
540 'with %s and process request', app)
541
541
542 return app(environ, start_response)
542 return app(environ, start_response)
543
543
544 return _git_stream
544 return _git_stream
545
545
546 def is_vcs_view(self, context, request):
546 def is_vcs_view(self, context, request):
547 """
547 """
548 View predicate that returns true if given backend is supported by
548 View predicate that returns true if given backend is supported by
549 defined remotes.
549 defined remotes.
550 """
550 """
551 backend = request.matchdict.get('backend')
551 backend = request.matchdict.get('backend')
552 return backend in self._remotes
552 return backend in self._remotes
553
553
554 def handle_vcs_exception(self, exception, request):
554 def handle_vcs_exception(self, exception, request):
555 _vcs_kind = getattr(exception, '_vcs_kind', '')
555 _vcs_kind = getattr(exception, '_vcs_kind', '')
556 if _vcs_kind == 'repo_locked':
556 if _vcs_kind == 'repo_locked':
557 # Get custom repo-locked status code if present.
557 # Get custom repo-locked status code if present.
558 status_code = request.headers.get('X-RC-Locked-Status-Code')
558 status_code = request.headers.get('X-RC-Locked-Status-Code')
559 return HTTPRepoLocked(
559 return HTTPRepoLocked(
560 title=exception.message, status_code=status_code)
560 title=exception.message, status_code=status_code)
561
561
562 elif _vcs_kind == 'repo_branch_protected':
562 elif _vcs_kind == 'repo_branch_protected':
563 # Get custom repo-branch-protected status code if present.
563 # Get custom repo-branch-protected status code if present.
564 return HTTPRepoBranchProtected(title=exception.message)
564 return HTTPRepoBranchProtected(title=exception.message)
565
565
566 exc_info = request.exc_info
566 exc_info = request.exc_info
567 store_exception(id(exc_info), exc_info)
567 store_exception(id(exc_info), exc_info)
568
568
569 traceback_info = 'unavailable'
569 traceback_info = 'unavailable'
570 if request.exc_info:
570 if request.exc_info:
571 exc_type, exc_value, exc_tb = request.exc_info
571 exc_type, exc_value, exc_tb = request.exc_info
572 traceback_info = ''.join(traceback.format_exception(exc_type, exc_value, exc_tb))
572 traceback_info = ''.join(traceback.format_exception(exc_type, exc_value, exc_tb))
573
573
574 log.error(
574 log.error(
575 'error occurred handling this request for path: %s, \n tb: %s',
575 'error occurred handling this request for path: %s, \n tb: %s',
576 request.path, traceback_info)
576 request.path, traceback_info)
577 raise exception
577 raise exception
578
578
579
579
580 class ResponseFilter(object):
580 class ResponseFilter(object):
581
581
582 def __init__(self, start_response):
582 def __init__(self, start_response):
583 self._start_response = start_response
583 self._start_response = start_response
584
584
585 def __call__(self, status, response_headers, exc_info=None):
585 def __call__(self, status, response_headers, exc_info=None):
586 headers = tuple(
586 headers = tuple(
587 (h, v) for h, v in response_headers
587 (h, v) for h, v in response_headers
588 if not wsgiref.util.is_hop_by_hop(h))
588 if not wsgiref.util.is_hop_by_hop(h))
589 return self._start_response(status, headers, exc_info)
589 return self._start_response(status, headers, exc_info)
590
590
591
591
592 def main(global_config, **settings):
592 def main(global_config, **settings):
593 if MercurialFactory:
593 if MercurialFactory:
594 hgpatches.patch_largefiles_capabilities()
594 hgpatches.patch_largefiles_capabilities()
595 hgpatches.patch_subrepo_type_mapping()
595 hgpatches.patch_subrepo_type_mapping()
596
596
597 app = HTTPApplication(settings=settings, global_config=global_config)
597 app = HTTPApplication(settings=settings, global_config=global_config)
598 return app.wsgi_app()
598 return app.wsgi_app()
@@ -1,65 +1,65 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # RhodeCode VCSServer provides access to different vcs backends via network.
3 # RhodeCode VCSServer provides access to different vcs backends via network.
4 # Copyright (C) 2014-2018 RhodeCode GmbH
4 # Copyright (C) 2014-2018 RhodeCode GmbH
5 #
5 #
6 # This program is free software; you can redistribute it and/or modify
6 # This program is free software; you can redistribute it and/or modify
7 # it under the terms of the GNU General Public License as published by
7 # it under the terms of the GNU General Public License as published by
8 # the Free Software Foundation; either version 3 of the License, or
8 # the Free Software Foundation; either version 3 of the License, or
9 # (at your option) any later version.
9 # (at your option) any later version.
10 #
10 #
11 # This program is distributed in the hope that it will be useful,
11 # This program is distributed in the hope that it will be useful,
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 # GNU General Public License for more details.
14 # GNU General Public License for more details.
15 #
15 #
16 # You should have received a copy of the GNU General Public License
16 # You should have received a copy of the GNU General Public License
17 # along with this program; if not, write to the Free Software Foundation,
17 # along with this program; if not, write to the Free Software Foundation,
18 # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
18 # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
19
19
20
20
21 import logging
21 import logging
22
22
23 from repoze.lru import LRUCache
23 from repoze.lru import LRUCache
24
24
25 from vcsserver.utils import safe_str
25 from vcsserver.utils import safe_str
26
26
27 log = logging.getLogger(__name__)
27 log = logging.getLogger(__name__)
28
28
29
29
30 class LRUDict(LRUCache):
30 class LRUDict(LRUCache):
31 """
31 """
32 Wrapper to provide partial dict access
32 Wrapper to provide partial dict access
33 """
33 """
34
34
35 def __setitem__(self, key, value):
35 def __setitem__(self, key, value):
36 return self.put(key, value)
36 return self.put(key, value)
37
37
38 def __getitem__(self, key):
38 def __getitem__(self, key):
39 return self.get(key)
39 return self.get(key)
40
40
41 def __contains__(self, key):
41 def __contains__(self, key):
42 return bool(self.get(key))
42 return bool(self.get(key))
43
43
44 def __delitem__(self, key):
44 def __delitem__(self, key):
45 del self.data[key]
45 del self.data[key]
46
46
47 def keys(self):
47 def keys(self):
48 return self.data.keys()
48 return self.data.keys()
49
49
50
50
51 class LRUDictDebug(LRUDict):
51 class LRUDictDebug(LRUDict):
52 """
52 """
53 Wrapper to provide some debug options
53 Wrapper to provide some debug options
54 """
54 """
55 def _report_keys(self):
55 def _report_keys(self):
56 elems_cnt = '%s/%s' % (len(self.keys()), self.size)
56 elems_cnt = '%s/%s' % (len(self.keys()), self.size)
57 # trick for pformat print it more nicely
57 # trick for pformat print it more nicely
58 fmt = '\n'
58 fmt = '\n'
59 for cnt, elem in enumerate(self.keys()):
59 for cnt, elem in enumerate(self.keys()):
60 fmt += '%s - %s\n' % (cnt+1, safe_str(elem))
60 fmt += '%s - %s\n' % (cnt+1, safe_str(elem))
61 log.debug('current LRU keys (%s):%s' % (elems_cnt, fmt))
61 log.debug('current LRU keys (%s):%s', elems_cnt, fmt)
62
62
63 def __getitem__(self, key):
63 def __getitem__(self, key):
64 self._report_keys()
64 self._report_keys()
65 return self.get(key)
65 return self.get(key)
@@ -1,60 +1,58 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-2018 RhodeCode GmbH
2 # Copyright (C) 2014-2018 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
18
19
19
20 import time
20 import time
21 import logging
21 import logging
22
22
23
23
24 from vcsserver.utils import safe_str
24 from vcsserver.utils import safe_str
25
25
26
26
27 log = logging.getLogger(__name__)
27 log = logging.getLogger(__name__)
28
28
29
29
30 def get_access_path(request):
30 def get_access_path(request):
31 environ = request.environ
31 environ = request.environ
32 return environ.get('PATH_INFO')
32 return environ.get('PATH_INFO')
33
33
34
34
35 class RequestWrapperTween(object):
35 class RequestWrapperTween(object):
36 def __init__(self, handler, registry):
36 def __init__(self, handler, registry):
37 self.handler = handler
37 self.handler = handler
38 self.registry = registry
38 self.registry = registry
39
39
40 # one-time configuration code goes here
40 # one-time configuration code goes here
41
41
42 def __call__(self, request):
42 def __call__(self, request):
43 start = time.time()
43 start = time.time()
44 try:
44 try:
45 response = self.handler(request)
45 response = self.handler(request)
46 finally:
46 finally:
47 end = time.time()
47 end = time.time()
48
48
49 log.info('IP: %s Request to path: `%s` time: %.3fs' % (
49 log.info('IP: %s Request to path: `%s` time: %.3fs',
50 '127.0.0.1',
50 '127.0.0.1', safe_str(get_access_path(request)), end - start)
51 safe_str(get_access_path(request)), end - start)
52 )
53
51
54 return response
52 return response
55
53
56
54
57 def includeme(config):
55 def includeme(config):
58 config.add_tween(
56 config.add_tween(
59 'vcsserver.tweens.RequestWrapperTween',
57 'vcsserver.tweens.RequestWrapperTween',
60 )
58 )
General Comments 0
You need to be logged in to leave comments. Login now