##// END OF EJS Templates
py3: remove compat module usage
super-admin -
r1038:ae22fbad python3
parent child Browse files
Show More
@@ -1,693 +1,693 b''
1 # RhodeCode VCSServer provides access to different vcs backends via network.
1 # RhodeCode VCSServer provides access to different vcs backends via network.
2 # Copyright (C) 2014-2020 RhodeCode GmbH
2 # Copyright (C) 2014-2020 RhodeCode GmbH
3 #
3 #
4 # This program is free software; you can redistribute it and/or modify
4 # This program is free software; you can redistribute it and/or modify
5 # it under the terms of the GNU General Public License as published by
5 # it under the terms of the GNU General Public License as published by
6 # the Free Software Foundation; either version 3 of the License, or
6 # the Free Software Foundation; either version 3 of the License, or
7 # (at your option) any later version.
7 # (at your option) any later version.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU General Public License
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software Foundation,
15 # along with this program; if not, write to the Free Software Foundation,
16 # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
16 # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
17
17
18 import os
18 import os
19 import sys
19 import sys
20 import base64
20 import base64
21 import locale
21 import locale
22 import logging
22 import logging
23 import uuid
23 import uuid
24 import 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 from io import StringIO
28 from io import StringIO
29
29
30 import simplejson as json
30 import simplejson as json
31 import msgpack
31 import msgpack
32 import configparser
32 from pyramid.config import Configurator
33 from pyramid.config import Configurator
33 from pyramid.settings import asbool, aslist
34 from pyramid.settings import asbool, aslist
34 from pyramid.wsgi import wsgiapp
35 from pyramid.wsgi import wsgiapp
35 from pyramid.compat import configparser
36 from pyramid.response import Response
36 from pyramid.response import Response
37
37
38 from vcsserver.utils import safe_int
38 from vcsserver.utils import safe_int
39
39
40 log = logging.getLogger(__name__)
40 log = logging.getLogger(__name__)
41
41
42 # due to Mercurial/glibc2.27 problems we need to detect if locale settings are
42 # due to Mercurial/glibc2.27 problems we need to detect if locale settings are
43 # causing problems and "fix" it in case they do and fallback to LC_ALL = C
43 # causing problems and "fix" it in case they do and fallback to LC_ALL = C
44
44
45 try:
45 try:
46 locale.setlocale(locale.LC_ALL, '')
46 locale.setlocale(locale.LC_ALL, '')
47 except locale.Error as e:
47 except locale.Error as e:
48 log.error(
48 log.error(
49 'LOCALE ERROR: failed to set LC_ALL, fallback to LC_ALL=C, org error: %s', e)
49 'LOCALE ERROR: failed to set LC_ALL, fallback to LC_ALL=C, org error: %s', e)
50 os.environ['LC_ALL'] = 'C'
50 os.environ['LC_ALL'] = 'C'
51
51
52 import vcsserver
52 import vcsserver
53 from vcsserver import remote_wsgi, scm_app, settings, hgpatches
53 from vcsserver import remote_wsgi, scm_app, settings, hgpatches
54 from vcsserver.git_lfs.app import GIT_LFS_CONTENT_TYPE, GIT_LFS_PROTO_PAT
54 from vcsserver.git_lfs.app import GIT_LFS_CONTENT_TYPE, GIT_LFS_PROTO_PAT
55 from vcsserver.echo_stub import remote_wsgi as remote_wsgi_stub
55 from vcsserver.echo_stub import remote_wsgi as remote_wsgi_stub
56 from vcsserver.echo_stub.echo_app import EchoApp
56 from vcsserver.echo_stub.echo_app import EchoApp
57 from vcsserver.exceptions import HTTPRepoLocked, HTTPRepoBranchProtected
57 from vcsserver.exceptions import HTTPRepoLocked, HTTPRepoBranchProtected
58 from vcsserver.lib.exc_tracking import store_exception
58 from vcsserver.lib.exc_tracking import store_exception
59 from vcsserver.server import VcsServer
59 from vcsserver.server import VcsServer
60
60
61
61
62 git_import_err = None
62 git_import_err = None
63 try:
63 try:
64 from vcsserver.git import GitFactory, GitRemote
64 from vcsserver.git import GitFactory, GitRemote
65 except ImportError as e:
65 except ImportError as e:
66 GitFactory = None
66 GitFactory = None
67 GitRemote = None
67 GitRemote = None
68 git_import_err = e
68 git_import_err = e
69
69
70 hg_import_err = None
70 hg_import_err = None
71 try:
71 try:
72 from vcsserver.hg import MercurialFactory, HgRemote
72 from vcsserver.hg import MercurialFactory, HgRemote
73 except ImportError as e:
73 except ImportError as e:
74 MercurialFactory = None
74 MercurialFactory = None
75 HgRemote = None
75 HgRemote = None
76 hg_import_err = e
76 hg_import_err = e
77
77
78 svn_import_err = None
78 svn_import_err = None
79 try:
79 try:
80 from vcsserver.svn import SubversionFactory, SvnRemote
80 from vcsserver.svn import SubversionFactory, SvnRemote
81 except ImportError as e:
81 except ImportError as e:
82 SubversionFactory = None
82 SubversionFactory = None
83 SvnRemote = None
83 SvnRemote = None
84 svn_import_err = e
84 svn_import_err = e
85
85
86
86
87 def _is_request_chunked(environ):
87 def _is_request_chunked(environ):
88 stream = environ.get('HTTP_TRANSFER_ENCODING', '') == 'chunked'
88 stream = environ.get('HTTP_TRANSFER_ENCODING', '') == 'chunked'
89 return stream
89 return stream
90
90
91
91
92 def _int_setting(settings, name, default):
92 def _int_setting(settings, name, default):
93 settings[name] = int(settings.get(name, default))
93 settings[name] = int(settings.get(name, default))
94 return settings[name]
94 return settings[name]
95
95
96
96
97 def _bool_setting(settings, name, default):
97 def _bool_setting(settings, name, default):
98 input_val = settings.get(name, default)
98 input_val = settings.get(name, default)
99 if isinstance(input_val, str):
99 if isinstance(input_val, str):
100 input_val = input_val.encode('utf8')
100 input_val = input_val.encode('utf8')
101 settings[name] = asbool(input_val)
101 settings[name] = asbool(input_val)
102 return settings[name]
102 return settings[name]
103
103
104
104
105 def _list_setting(settings, name, default):
105 def _list_setting(settings, name, default):
106 raw_value = settings.get(name, default)
106 raw_value = settings.get(name, default)
107
107
108 # Otherwise we assume it uses pyramids space/newline separation.
108 # Otherwise we assume it uses pyramids space/newline separation.
109 settings[name] = aslist(raw_value)
109 settings[name] = aslist(raw_value)
110 return settings[name]
110 return settings[name]
111
111
112
112
113 def _string_setting(settings, name, default, lower=True, default_when_empty=False):
113 def _string_setting(settings, name, default, lower=True, default_when_empty=False):
114 value = settings.get(name, default)
114 value = settings.get(name, default)
115
115
116 if default_when_empty and not value:
116 if default_when_empty and not value:
117 # use default value when value is empty
117 # use default value when value is empty
118 value = default
118 value = default
119
119
120 if lower:
120 if lower:
121 value = value.lower()
121 value = value.lower()
122 settings[name] = value
122 settings[name] = value
123 return settings[name]
123 return settings[name]
124
124
125
125
126 class VCS(object):
126 class VCS(object):
127 def __init__(self, locale_conf=None, cache_config=None):
127 def __init__(self, locale_conf=None, cache_config=None):
128 self.locale = locale_conf
128 self.locale = locale_conf
129 self.cache_config = cache_config
129 self.cache_config = cache_config
130 self._configure_locale()
130 self._configure_locale()
131
131
132 if GitFactory and GitRemote:
132 if GitFactory and GitRemote:
133 git_factory = GitFactory()
133 git_factory = GitFactory()
134 self._git_remote = GitRemote(git_factory)
134 self._git_remote = GitRemote(git_factory)
135 else:
135 else:
136 log.error("Git client import failed: %s", git_import_err)
136 log.error("Git client import failed: %s", git_import_err)
137
137
138 if MercurialFactory and HgRemote:
138 if MercurialFactory and HgRemote:
139 hg_factory = MercurialFactory()
139 hg_factory = MercurialFactory()
140 self._hg_remote = HgRemote(hg_factory)
140 self._hg_remote = HgRemote(hg_factory)
141 else:
141 else:
142 log.error("Mercurial client import failed: %s", hg_import_err)
142 log.error("Mercurial client import failed: %s", hg_import_err)
143
143
144 if SubversionFactory and SvnRemote:
144 if SubversionFactory and SvnRemote:
145 svn_factory = SubversionFactory()
145 svn_factory = SubversionFactory()
146
146
147 # hg factory is used for svn url validation
147 # hg factory is used for svn url validation
148 hg_factory = MercurialFactory()
148 hg_factory = MercurialFactory()
149 self._svn_remote = SvnRemote(svn_factory, hg_factory=hg_factory)
149 self._svn_remote = SvnRemote(svn_factory, hg_factory=hg_factory)
150 else:
150 else:
151 log.error("Subversion client import failed: %s", svn_import_err)
151 log.error("Subversion client import failed: %s", svn_import_err)
152
152
153 self._vcsserver = VcsServer()
153 self._vcsserver = VcsServer()
154
154
155 def _configure_locale(self):
155 def _configure_locale(self):
156 if self.locale:
156 if self.locale:
157 log.info('Settings locale: `LC_ALL` to %s', self.locale)
157 log.info('Settings locale: `LC_ALL` to %s', self.locale)
158 else:
158 else:
159 log.info('Configuring locale subsystem based on environment variables')
159 log.info('Configuring locale subsystem based on environment variables')
160 try:
160 try:
161 # If self.locale is the empty string, then the locale
161 # If self.locale is the empty string, then the locale
162 # module will use the environment variables. See the
162 # module will use the environment variables. See the
163 # documentation of the package `locale`.
163 # documentation of the package `locale`.
164 locale.setlocale(locale.LC_ALL, self.locale)
164 locale.setlocale(locale.LC_ALL, self.locale)
165
165
166 language_code, encoding = locale.getlocale()
166 language_code, encoding = locale.getlocale()
167 log.info(
167 log.info(
168 'Locale set to language code "%s" with encoding "%s".',
168 'Locale set to language code "%s" with encoding "%s".',
169 language_code, encoding)
169 language_code, encoding)
170 except locale.Error:
170 except locale.Error:
171 log.exception('Cannot set locale, not configuring the locale system')
171 log.exception('Cannot set locale, not configuring the locale system')
172
172
173
173
174 class WsgiProxy(object):
174 class WsgiProxy(object):
175 def __init__(self, wsgi):
175 def __init__(self, wsgi):
176 self.wsgi = wsgi
176 self.wsgi = wsgi
177
177
178 def __call__(self, environ, start_response):
178 def __call__(self, environ, start_response):
179 input_data = environ['wsgi.input'].read()
179 input_data = environ['wsgi.input'].read()
180 input_data = msgpack.unpackb(input_data)
180 input_data = msgpack.unpackb(input_data)
181
181
182 error = None
182 error = None
183 try:
183 try:
184 data, status, headers = self.wsgi.handle(
184 data, status, headers = self.wsgi.handle(
185 input_data['environment'], input_data['input_data'],
185 input_data['environment'], input_data['input_data'],
186 *input_data['args'], **input_data['kwargs'])
186 *input_data['args'], **input_data['kwargs'])
187 except Exception as e:
187 except Exception as e:
188 data, status, headers = [], None, None
188 data, status, headers = [], None, None
189 error = {
189 error = {
190 'message': str(e),
190 'message': str(e),
191 '_vcs_kind': getattr(e, '_vcs_kind', None)
191 '_vcs_kind': getattr(e, '_vcs_kind', None)
192 }
192 }
193
193
194 start_response(200, {})
194 start_response(200, {})
195 return self._iterator(error, status, headers, data)
195 return self._iterator(error, status, headers, data)
196
196
197 def _iterator(self, error, status, headers, data):
197 def _iterator(self, error, status, headers, data):
198 initial_data = [
198 initial_data = [
199 error,
199 error,
200 status,
200 status,
201 headers,
201 headers,
202 ]
202 ]
203
203
204 for d in chain(initial_data, data):
204 for d in chain(initial_data, data):
205 yield msgpack.packb(d)
205 yield msgpack.packb(d)
206
206
207
207
208 def not_found(request):
208 def not_found(request):
209 return {'status': '404 NOT FOUND'}
209 return {'status': '404 NOT FOUND'}
210
210
211
211
212 class VCSViewPredicate(object):
212 class VCSViewPredicate(object):
213 def __init__(self, val, config):
213 def __init__(self, val, config):
214 self.remotes = val
214 self.remotes = val
215
215
216 def text(self):
216 def text(self):
217 return 'vcs view method = %s' % (list(self.remotes.keys()),)
217 return 'vcs view method = %s' % (list(self.remotes.keys()),)
218
218
219 phash = text
219 phash = text
220
220
221 def __call__(self, context, request):
221 def __call__(self, context, request):
222 """
222 """
223 View predicate that returns true if given backend is supported by
223 View predicate that returns true if given backend is supported by
224 defined remotes.
224 defined remotes.
225 """
225 """
226 backend = request.matchdict.get('backend')
226 backend = request.matchdict.get('backend')
227 return backend in self.remotes
227 return backend in self.remotes
228
228
229
229
230 class HTTPApplication(object):
230 class HTTPApplication(object):
231 ALLOWED_EXCEPTIONS = ('KeyError', 'URLError')
231 ALLOWED_EXCEPTIONS = ('KeyError', 'URLError')
232
232
233 remote_wsgi = remote_wsgi
233 remote_wsgi = remote_wsgi
234 _use_echo_app = False
234 _use_echo_app = False
235
235
236 def __init__(self, settings=None, global_config=None):
236 def __init__(self, settings=None, global_config=None):
237 self._sanitize_settings_and_apply_defaults(settings)
237 self._sanitize_settings_and_apply_defaults(settings)
238
238
239 self.config = Configurator(settings=settings)
239 self.config = Configurator(settings=settings)
240 self.global_config = global_config
240 self.global_config = global_config
241 self.config.include('vcsserver.lib.rc_cache')
241 self.config.include('vcsserver.lib.rc_cache')
242
242
243 settings_locale = settings.get('locale', '') or 'en_US.UTF-8'
243 settings_locale = settings.get('locale', '') or 'en_US.UTF-8'
244 vcs = VCS(locale_conf=settings_locale, cache_config=settings)
244 vcs = VCS(locale_conf=settings_locale, cache_config=settings)
245 self._remotes = {
245 self._remotes = {
246 'hg': vcs._hg_remote,
246 'hg': vcs._hg_remote,
247 'git': vcs._git_remote,
247 'git': vcs._git_remote,
248 'svn': vcs._svn_remote,
248 'svn': vcs._svn_remote,
249 'server': vcs._vcsserver,
249 'server': vcs._vcsserver,
250 }
250 }
251 if settings.get('dev.use_echo_app', 'false').lower() == 'true':
251 if settings.get('dev.use_echo_app', 'false').lower() == 'true':
252 self._use_echo_app = True
252 self._use_echo_app = True
253 log.warning("Using EchoApp for VCS operations.")
253 log.warning("Using EchoApp for VCS operations.")
254 self.remote_wsgi = remote_wsgi_stub
254 self.remote_wsgi = remote_wsgi_stub
255
255
256 self._configure_settings(global_config, settings)
256 self._configure_settings(global_config, settings)
257 self._configure()
257 self._configure()
258
258
259 def _configure_settings(self, global_config, app_settings):
259 def _configure_settings(self, global_config, app_settings):
260 """
260 """
261 Configure the settings module.
261 Configure the settings module.
262 """
262 """
263 settings_merged = global_config.copy()
263 settings_merged = global_config.copy()
264 settings_merged.update(app_settings)
264 settings_merged.update(app_settings)
265
265
266 git_path = app_settings.get('git_path', None)
266 git_path = app_settings.get('git_path', None)
267 if git_path:
267 if git_path:
268 settings.GIT_EXECUTABLE = git_path
268 settings.GIT_EXECUTABLE = git_path
269 binary_dir = app_settings.get('core.binary_dir', None)
269 binary_dir = app_settings.get('core.binary_dir', None)
270 if binary_dir:
270 if binary_dir:
271 settings.BINARY_DIR = binary_dir
271 settings.BINARY_DIR = binary_dir
272
272
273 # Store the settings to make them available to other modules.
273 # Store the settings to make them available to other modules.
274 vcsserver.PYRAMID_SETTINGS = settings_merged
274 vcsserver.PYRAMID_SETTINGS = settings_merged
275 vcsserver.CONFIG = settings_merged
275 vcsserver.CONFIG = settings_merged
276
276
277 def _sanitize_settings_and_apply_defaults(self, settings):
277 def _sanitize_settings_and_apply_defaults(self, settings):
278 temp_store = tempfile.gettempdir()
278 temp_store = tempfile.gettempdir()
279 default_cache_dir = os.path.join(temp_store, 'rc_cache')
279 default_cache_dir = os.path.join(temp_store, 'rc_cache')
280
280
281 # save default, cache dir, and use it for all backends later.
281 # save default, cache dir, and use it for all backends later.
282 default_cache_dir = _string_setting(
282 default_cache_dir = _string_setting(
283 settings,
283 settings,
284 'cache_dir',
284 'cache_dir',
285 default_cache_dir, lower=False, default_when_empty=True)
285 default_cache_dir, lower=False, default_when_empty=True)
286
286
287 # ensure we have our dir created
287 # ensure we have our dir created
288 if not os.path.isdir(default_cache_dir):
288 if not os.path.isdir(default_cache_dir):
289 os.makedirs(default_cache_dir, mode=0o755)
289 os.makedirs(default_cache_dir, mode=0o755)
290
290
291 # exception store cache
291 # exception store cache
292 _string_setting(
292 _string_setting(
293 settings,
293 settings,
294 'exception_tracker.store_path',
294 'exception_tracker.store_path',
295 temp_store, lower=False, default_when_empty=True)
295 temp_store, lower=False, default_when_empty=True)
296
296
297 # repo_object cache
297 # repo_object cache
298 _string_setting(
298 _string_setting(
299 settings,
299 settings,
300 'rc_cache.repo_object.backend',
300 'rc_cache.repo_object.backend',
301 'dogpile.cache.rc.file_namespace', lower=False)
301 'dogpile.cache.rc.file_namespace', lower=False)
302 _int_setting(
302 _int_setting(
303 settings,
303 settings,
304 'rc_cache.repo_object.expiration_time',
304 'rc_cache.repo_object.expiration_time',
305 30 * 24 * 60 * 60)
305 30 * 24 * 60 * 60)
306 _string_setting(
306 _string_setting(
307 settings,
307 settings,
308 'rc_cache.repo_object.arguments.filename',
308 'rc_cache.repo_object.arguments.filename',
309 os.path.join(default_cache_dir, 'vcsserver_cache_1'), lower=False)
309 os.path.join(default_cache_dir, 'vcsserver_cache_1'), lower=False)
310
310
311 def _configure(self):
311 def _configure(self):
312 self.config.add_renderer(name='msgpack', factory=self._msgpack_renderer_factory)
312 self.config.add_renderer(name='msgpack', factory=self._msgpack_renderer_factory)
313
313
314 self.config.add_route('service', '/_service')
314 self.config.add_route('service', '/_service')
315 self.config.add_route('status', '/status')
315 self.config.add_route('status', '/status')
316 self.config.add_route('hg_proxy', '/proxy/hg')
316 self.config.add_route('hg_proxy', '/proxy/hg')
317 self.config.add_route('git_proxy', '/proxy/git')
317 self.config.add_route('git_proxy', '/proxy/git')
318
318
319 # rpc methods
319 # rpc methods
320 self.config.add_route('vcs', '/{backend}')
320 self.config.add_route('vcs', '/{backend}')
321
321
322 # streaming rpc remote methods
322 # streaming rpc remote methods
323 self.config.add_route('vcs_stream', '/{backend}/stream')
323 self.config.add_route('vcs_stream', '/{backend}/stream')
324
324
325 # vcs operations clone/push as streaming
325 # vcs operations clone/push as streaming
326 self.config.add_route('stream_git', '/stream/git/*repo_name')
326 self.config.add_route('stream_git', '/stream/git/*repo_name')
327 self.config.add_route('stream_hg', '/stream/hg/*repo_name')
327 self.config.add_route('stream_hg', '/stream/hg/*repo_name')
328
328
329 self.config.add_view(self.status_view, route_name='status', renderer='json')
329 self.config.add_view(self.status_view, route_name='status', renderer='json')
330 self.config.add_view(self.service_view, route_name='service', renderer='msgpack')
330 self.config.add_view(self.service_view, route_name='service', renderer='msgpack')
331
331
332 self.config.add_view(self.hg_proxy(), route_name='hg_proxy')
332 self.config.add_view(self.hg_proxy(), route_name='hg_proxy')
333 self.config.add_view(self.git_proxy(), route_name='git_proxy')
333 self.config.add_view(self.git_proxy(), route_name='git_proxy')
334 self.config.add_view(self.vcs_view, route_name='vcs', renderer='msgpack',
334 self.config.add_view(self.vcs_view, route_name='vcs', renderer='msgpack',
335 vcs_view=self._remotes)
335 vcs_view=self._remotes)
336 self.config.add_view(self.vcs_stream_view, route_name='vcs_stream',
336 self.config.add_view(self.vcs_stream_view, route_name='vcs_stream',
337 vcs_view=self._remotes)
337 vcs_view=self._remotes)
338
338
339 self.config.add_view(self.hg_stream(), route_name='stream_hg')
339 self.config.add_view(self.hg_stream(), route_name='stream_hg')
340 self.config.add_view(self.git_stream(), route_name='stream_git')
340 self.config.add_view(self.git_stream(), route_name='stream_git')
341
341
342 self.config.add_view_predicate('vcs_view', VCSViewPredicate)
342 self.config.add_view_predicate('vcs_view', VCSViewPredicate)
343
343
344 self.config.add_notfound_view(not_found, renderer='json')
344 self.config.add_notfound_view(not_found, renderer='json')
345
345
346 self.config.add_view(self.handle_vcs_exception, context=Exception)
346 self.config.add_view(self.handle_vcs_exception, context=Exception)
347
347
348 self.config.add_tween(
348 self.config.add_tween(
349 'vcsserver.tweens.request_wrapper.RequestWrapperTween',
349 'vcsserver.tweens.request_wrapper.RequestWrapperTween',
350 )
350 )
351 self.config.add_request_method(
351 self.config.add_request_method(
352 'vcsserver.lib.request_counter.get_request_counter',
352 'vcsserver.lib.request_counter.get_request_counter',
353 'request_count')
353 'request_count')
354
354
355 def wsgi_app(self):
355 def wsgi_app(self):
356 return self.config.make_wsgi_app()
356 return self.config.make_wsgi_app()
357
357
358 def _vcs_view_params(self, request):
358 def _vcs_view_params(self, request):
359 remote = self._remotes[request.matchdict['backend']]
359 remote = self._remotes[request.matchdict['backend']]
360 payload = msgpack.unpackb(request.body, use_list=True)
360 payload = msgpack.unpackb(request.body, use_list=True)
361 method = payload.get('method')
361 method = payload.get('method')
362 params = payload['params']
362 params = payload['params']
363 wire = params.get('wire')
363 wire = params.get('wire')
364 args = params.get('args')
364 args = params.get('args')
365 kwargs = params.get('kwargs')
365 kwargs = params.get('kwargs')
366 context_uid = None
366 context_uid = None
367
367
368 if wire:
368 if wire:
369 try:
369 try:
370 wire['context'] = context_uid = uuid.UUID(wire['context'])
370 wire['context'] = context_uid = uuid.UUID(wire['context'])
371 except KeyError:
371 except KeyError:
372 pass
372 pass
373 args.insert(0, wire)
373 args.insert(0, wire)
374 repo_state_uid = wire.get('repo_state_uid') if wire else None
374 repo_state_uid = wire.get('repo_state_uid') if wire else None
375
375
376 # NOTE(marcink): trading complexity for slight performance
376 # NOTE(marcink): trading complexity for slight performance
377 if log.isEnabledFor(logging.DEBUG):
377 if log.isEnabledFor(logging.DEBUG):
378 no_args_methods = [
378 no_args_methods = [
379 'archive_repo'
379 'archive_repo'
380 ]
380 ]
381 if method in no_args_methods:
381 if method in no_args_methods:
382 call_args = ''
382 call_args = ''
383 else:
383 else:
384 call_args = args[1:]
384 call_args = args[1:]
385
385
386 log.debug('method requested:%s with args:%s kwargs:%s context_uid: %s, repo_state_uid:%s',
386 log.debug('method requested:%s with args:%s kwargs:%s context_uid: %s, repo_state_uid:%s',
387 method, call_args, kwargs, context_uid, repo_state_uid)
387 method, call_args, kwargs, context_uid, repo_state_uid)
388
388
389 return payload, remote, method, args, kwargs
389 return payload, remote, method, args, kwargs
390
390
391 def vcs_view(self, request):
391 def vcs_view(self, request):
392
392
393 payload, remote, method, args, kwargs = self._vcs_view_params(request)
393 payload, remote, method, args, kwargs = self._vcs_view_params(request)
394 payload_id = payload.get('id')
394 payload_id = payload.get('id')
395
395
396 try:
396 try:
397 resp = getattr(remote, method)(*args, **kwargs)
397 resp = getattr(remote, method)(*args, **kwargs)
398 except Exception as e:
398 except Exception as e:
399 exc_info = list(sys.exc_info())
399 exc_info = list(sys.exc_info())
400 exc_type, exc_value, exc_traceback = exc_info
400 exc_type, exc_value, exc_traceback = exc_info
401
401
402 org_exc = getattr(e, '_org_exc', None)
402 org_exc = getattr(e, '_org_exc', None)
403 org_exc_name = None
403 org_exc_name = None
404 org_exc_tb = ''
404 org_exc_tb = ''
405 if org_exc:
405 if org_exc:
406 org_exc_name = org_exc.__class__.__name__
406 org_exc_name = org_exc.__class__.__name__
407 org_exc_tb = getattr(e, '_org_exc_tb', '')
407 org_exc_tb = getattr(e, '_org_exc_tb', '')
408 # replace our "faked" exception with our org
408 # replace our "faked" exception with our org
409 exc_info[0] = org_exc.__class__
409 exc_info[0] = org_exc.__class__
410 exc_info[1] = org_exc
410 exc_info[1] = org_exc
411
411
412 should_store_exc = True
412 should_store_exc = True
413 if org_exc:
413 if org_exc:
414 def get_exc_fqn(_exc_obj):
414 def get_exc_fqn(_exc_obj):
415 module_name = getattr(org_exc.__class__, '__module__', 'UNKNOWN')
415 module_name = getattr(org_exc.__class__, '__module__', 'UNKNOWN')
416 return module_name + '.' + org_exc_name
416 return module_name + '.' + org_exc_name
417
417
418 exc_fqn = get_exc_fqn(org_exc)
418 exc_fqn = get_exc_fqn(org_exc)
419
419
420 if exc_fqn in ['mercurial.error.RepoLookupError',
420 if exc_fqn in ['mercurial.error.RepoLookupError',
421 'vcsserver.exceptions.RefNotFoundException']:
421 'vcsserver.exceptions.RefNotFoundException']:
422 should_store_exc = False
422 should_store_exc = False
423
423
424 if should_store_exc:
424 if should_store_exc:
425 store_exception(id(exc_info), exc_info)
425 store_exception(id(exc_info), exc_info)
426
426
427 tb_info = ''.join(
427 tb_info = ''.join(
428 traceback.format_exception(exc_type, exc_value, exc_traceback))
428 traceback.format_exception(exc_type, exc_value, exc_traceback))
429
429
430 type_ = e.__class__.__name__
430 type_ = e.__class__.__name__
431 if type_ not in self.ALLOWED_EXCEPTIONS:
431 if type_ not in self.ALLOWED_EXCEPTIONS:
432 type_ = None
432 type_ = None
433
433
434 resp = {
434 resp = {
435 'id': payload_id,
435 'id': payload_id,
436 'error': {
436 'error': {
437 'message': e.message,
437 'message': e.message,
438 'traceback': tb_info,
438 'traceback': tb_info,
439 'org_exc': org_exc_name,
439 'org_exc': org_exc_name,
440 'org_exc_tb': org_exc_tb,
440 'org_exc_tb': org_exc_tb,
441 'type': type_
441 'type': type_
442 }
442 }
443 }
443 }
444 try:
444 try:
445 resp['error']['_vcs_kind'] = getattr(e, '_vcs_kind', None)
445 resp['error']['_vcs_kind'] = getattr(e, '_vcs_kind', None)
446 except AttributeError:
446 except AttributeError:
447 pass
447 pass
448 else:
448 else:
449 resp = {
449 resp = {
450 'id': payload_id,
450 'id': payload_id,
451 'result': resp
451 'result': resp
452 }
452 }
453
453
454 return resp
454 return resp
455
455
456 def vcs_stream_view(self, request):
456 def vcs_stream_view(self, request):
457 payload, remote, method, args, kwargs = self._vcs_view_params(request)
457 payload, remote, method, args, kwargs = self._vcs_view_params(request)
458 # this method has a stream: marker we remove it here
458 # this method has a stream: marker we remove it here
459 method = method.split('stream:')[-1]
459 method = method.split('stream:')[-1]
460 chunk_size = safe_int(payload.get('chunk_size')) or 4096
460 chunk_size = safe_int(payload.get('chunk_size')) or 4096
461
461
462 try:
462 try:
463 resp = getattr(remote, method)(*args, **kwargs)
463 resp = getattr(remote, method)(*args, **kwargs)
464 except Exception as e:
464 except Exception as e:
465 raise
465 raise
466
466
467 def get_chunked_data(method_resp):
467 def get_chunked_data(method_resp):
468 stream = StringIO(method_resp)
468 stream = StringIO(method_resp)
469 while 1:
469 while 1:
470 chunk = stream.read(chunk_size)
470 chunk = stream.read(chunk_size)
471 if not chunk:
471 if not chunk:
472 break
472 break
473 yield chunk
473 yield chunk
474
474
475 response = Response(app_iter=get_chunked_data(resp))
475 response = Response(app_iter=get_chunked_data(resp))
476 response.content_type = 'application/octet-stream'
476 response.content_type = 'application/octet-stream'
477
477
478 return response
478 return response
479
479
480 def status_view(self, request):
480 def status_view(self, request):
481 import vcsserver
481 import vcsserver
482 return {'status': 'OK', 'vcsserver_version': vcsserver.__version__,
482 return {'status': 'OK', 'vcsserver_version': vcsserver.__version__,
483 'pid': os.getpid()}
483 'pid': os.getpid()}
484
484
485 def service_view(self, request):
485 def service_view(self, request):
486 import vcsserver
486 import vcsserver
487
487
488 payload = msgpack.unpackb(request.body, use_list=True)
488 payload = msgpack.unpackb(request.body, use_list=True)
489 server_config, app_config = {}, {}
489 server_config, app_config = {}, {}
490
490
491 try:
491 try:
492 path = self.global_config['__file__']
492 path = self.global_config['__file__']
493 config = configparser.RawConfigParser()
493 config = configparser.RawConfigParser()
494
494
495 config.read(path)
495 config.read(path)
496
496
497 if config.has_section('server:main'):
497 if config.has_section('server:main'):
498 server_config = dict(config.items('server:main'))
498 server_config = dict(config.items('server:main'))
499 if config.has_section('app:main'):
499 if config.has_section('app:main'):
500 app_config = dict(config.items('app:main'))
500 app_config = dict(config.items('app:main'))
501
501
502 except Exception:
502 except Exception:
503 log.exception('Failed to read .ini file for display')
503 log.exception('Failed to read .ini file for display')
504
504
505 environ = list(os.environ.items())
505 environ = list(os.environ.items())
506
506
507 resp = {
507 resp = {
508 'id': payload.get('id'),
508 'id': payload.get('id'),
509 'result': dict(
509 'result': dict(
510 version=vcsserver.__version__,
510 version=vcsserver.__version__,
511 config=server_config,
511 config=server_config,
512 app_config=app_config,
512 app_config=app_config,
513 environ=environ,
513 environ=environ,
514 payload=payload,
514 payload=payload,
515 )
515 )
516 }
516 }
517 return resp
517 return resp
518
518
519 def _msgpack_renderer_factory(self, info):
519 def _msgpack_renderer_factory(self, info):
520 def _render(value, system):
520 def _render(value, system):
521 request = system.get('request')
521 request = system.get('request')
522 if request is not None:
522 if request is not None:
523 response = request.response
523 response = request.response
524 ct = response.content_type
524 ct = response.content_type
525 if ct == response.default_content_type:
525 if ct == response.default_content_type:
526 response.content_type = 'application/x-msgpack'
526 response.content_type = 'application/x-msgpack'
527 return msgpack.packb(value)
527 return msgpack.packb(value)
528 return _render
528 return _render
529
529
530 def set_env_from_config(self, environ, config):
530 def set_env_from_config(self, environ, config):
531 dict_conf = {}
531 dict_conf = {}
532 try:
532 try:
533 for elem in config:
533 for elem in config:
534 if elem[0] == 'rhodecode':
534 if elem[0] == 'rhodecode':
535 dict_conf = json.loads(elem[2])
535 dict_conf = json.loads(elem[2])
536 break
536 break
537 except Exception:
537 except Exception:
538 log.exception('Failed to fetch SCM CONFIG')
538 log.exception('Failed to fetch SCM CONFIG')
539 return
539 return
540
540
541 username = dict_conf.get('username')
541 username = dict_conf.get('username')
542 if username:
542 if username:
543 environ['REMOTE_USER'] = username
543 environ['REMOTE_USER'] = username
544 # mercurial specific, some extension api rely on this
544 # mercurial specific, some extension api rely on this
545 environ['HGUSER'] = username
545 environ['HGUSER'] = username
546
546
547 ip = dict_conf.get('ip')
547 ip = dict_conf.get('ip')
548 if ip:
548 if ip:
549 environ['REMOTE_HOST'] = ip
549 environ['REMOTE_HOST'] = ip
550
550
551 if _is_request_chunked(environ):
551 if _is_request_chunked(environ):
552 # set the compatibility flag for webob
552 # set the compatibility flag for webob
553 environ['wsgi.input_terminated'] = True
553 environ['wsgi.input_terminated'] = True
554
554
555 def hg_proxy(self):
555 def hg_proxy(self):
556 @wsgiapp
556 @wsgiapp
557 def _hg_proxy(environ, start_response):
557 def _hg_proxy(environ, start_response):
558 app = WsgiProxy(self.remote_wsgi.HgRemoteWsgi())
558 app = WsgiProxy(self.remote_wsgi.HgRemoteWsgi())
559 return app(environ, start_response)
559 return app(environ, start_response)
560 return _hg_proxy
560 return _hg_proxy
561
561
562 def git_proxy(self):
562 def git_proxy(self):
563 @wsgiapp
563 @wsgiapp
564 def _git_proxy(environ, start_response):
564 def _git_proxy(environ, start_response):
565 app = WsgiProxy(self.remote_wsgi.GitRemoteWsgi())
565 app = WsgiProxy(self.remote_wsgi.GitRemoteWsgi())
566 return app(environ, start_response)
566 return app(environ, start_response)
567 return _git_proxy
567 return _git_proxy
568
568
569 def hg_stream(self):
569 def hg_stream(self):
570 if self._use_echo_app:
570 if self._use_echo_app:
571 @wsgiapp
571 @wsgiapp
572 def _hg_stream(environ, start_response):
572 def _hg_stream(environ, start_response):
573 app = EchoApp('fake_path', 'fake_name', None)
573 app = EchoApp('fake_path', 'fake_name', None)
574 return app(environ, start_response)
574 return app(environ, start_response)
575 return _hg_stream
575 return _hg_stream
576 else:
576 else:
577 @wsgiapp
577 @wsgiapp
578 def _hg_stream(environ, start_response):
578 def _hg_stream(environ, start_response):
579 log.debug('http-app: handling hg stream')
579 log.debug('http-app: handling hg stream')
580 repo_path = environ['HTTP_X_RC_REPO_PATH']
580 repo_path = environ['HTTP_X_RC_REPO_PATH']
581 repo_name = environ['HTTP_X_RC_REPO_NAME']
581 repo_name = environ['HTTP_X_RC_REPO_NAME']
582 packed_config = base64.b64decode(
582 packed_config = base64.b64decode(
583 environ['HTTP_X_RC_REPO_CONFIG'])
583 environ['HTTP_X_RC_REPO_CONFIG'])
584 config = msgpack.unpackb(packed_config)
584 config = msgpack.unpackb(packed_config)
585 app = scm_app.create_hg_wsgi_app(
585 app = scm_app.create_hg_wsgi_app(
586 repo_path, repo_name, config)
586 repo_path, repo_name, config)
587
587
588 # Consistent path information for hgweb
588 # Consistent path information for hgweb
589 environ['PATH_INFO'] = environ['HTTP_X_RC_PATH_INFO']
589 environ['PATH_INFO'] = environ['HTTP_X_RC_PATH_INFO']
590 environ['REPO_NAME'] = repo_name
590 environ['REPO_NAME'] = repo_name
591 self.set_env_from_config(environ, config)
591 self.set_env_from_config(environ, config)
592
592
593 log.debug('http-app: starting app handler '
593 log.debug('http-app: starting app handler '
594 'with %s and process request', app)
594 'with %s and process request', app)
595 return app(environ, ResponseFilter(start_response))
595 return app(environ, ResponseFilter(start_response))
596 return _hg_stream
596 return _hg_stream
597
597
598 def git_stream(self):
598 def git_stream(self):
599 if self._use_echo_app:
599 if self._use_echo_app:
600 @wsgiapp
600 @wsgiapp
601 def _git_stream(environ, start_response):
601 def _git_stream(environ, start_response):
602 app = EchoApp('fake_path', 'fake_name', None)
602 app = EchoApp('fake_path', 'fake_name', None)
603 return app(environ, start_response)
603 return app(environ, start_response)
604 return _git_stream
604 return _git_stream
605 else:
605 else:
606 @wsgiapp
606 @wsgiapp
607 def _git_stream(environ, start_response):
607 def _git_stream(environ, start_response):
608 log.debug('http-app: handling git stream')
608 log.debug('http-app: handling git stream')
609 repo_path = environ['HTTP_X_RC_REPO_PATH']
609 repo_path = environ['HTTP_X_RC_REPO_PATH']
610 repo_name = environ['HTTP_X_RC_REPO_NAME']
610 repo_name = environ['HTTP_X_RC_REPO_NAME']
611 packed_config = base64.b64decode(
611 packed_config = base64.b64decode(
612 environ['HTTP_X_RC_REPO_CONFIG'])
612 environ['HTTP_X_RC_REPO_CONFIG'])
613 config = msgpack.unpackb(packed_config)
613 config = msgpack.unpackb(packed_config)
614
614
615 environ['PATH_INFO'] = environ['HTTP_X_RC_PATH_INFO']
615 environ['PATH_INFO'] = environ['HTTP_X_RC_PATH_INFO']
616 self.set_env_from_config(environ, config)
616 self.set_env_from_config(environ, config)
617
617
618 content_type = environ.get('CONTENT_TYPE', '')
618 content_type = environ.get('CONTENT_TYPE', '')
619
619
620 path = environ['PATH_INFO']
620 path = environ['PATH_INFO']
621 is_lfs_request = GIT_LFS_CONTENT_TYPE in content_type
621 is_lfs_request = GIT_LFS_CONTENT_TYPE in content_type
622 log.debug(
622 log.debug(
623 'LFS: Detecting if request `%s` is LFS server path based '
623 'LFS: Detecting if request `%s` is LFS server path based '
624 'on content type:`%s`, is_lfs:%s',
624 'on content type:`%s`, is_lfs:%s',
625 path, content_type, is_lfs_request)
625 path, content_type, is_lfs_request)
626
626
627 if not is_lfs_request:
627 if not is_lfs_request:
628 # fallback detection by path
628 # fallback detection by path
629 if GIT_LFS_PROTO_PAT.match(path):
629 if GIT_LFS_PROTO_PAT.match(path):
630 is_lfs_request = True
630 is_lfs_request = True
631 log.debug(
631 log.debug(
632 'LFS: fallback detection by path of: `%s`, is_lfs:%s',
632 'LFS: fallback detection by path of: `%s`, is_lfs:%s',
633 path, is_lfs_request)
633 path, is_lfs_request)
634
634
635 if is_lfs_request:
635 if is_lfs_request:
636 app = scm_app.create_git_lfs_wsgi_app(
636 app = scm_app.create_git_lfs_wsgi_app(
637 repo_path, repo_name, config)
637 repo_path, repo_name, config)
638 else:
638 else:
639 app = scm_app.create_git_wsgi_app(
639 app = scm_app.create_git_wsgi_app(
640 repo_path, repo_name, config)
640 repo_path, repo_name, config)
641
641
642 log.debug('http-app: starting app handler '
642 log.debug('http-app: starting app handler '
643 'with %s and process request', app)
643 'with %s and process request', app)
644
644
645 return app(environ, start_response)
645 return app(environ, start_response)
646
646
647 return _git_stream
647 return _git_stream
648
648
649 def handle_vcs_exception(self, exception, request):
649 def handle_vcs_exception(self, exception, request):
650 _vcs_kind = getattr(exception, '_vcs_kind', '')
650 _vcs_kind = getattr(exception, '_vcs_kind', '')
651 if _vcs_kind == 'repo_locked':
651 if _vcs_kind == 'repo_locked':
652 # Get custom repo-locked status code if present.
652 # Get custom repo-locked status code if present.
653 status_code = request.headers.get('X-RC-Locked-Status-Code')
653 status_code = request.headers.get('X-RC-Locked-Status-Code')
654 return HTTPRepoLocked(
654 return HTTPRepoLocked(
655 title=exception.message, status_code=status_code)
655 title=exception.message, status_code=status_code)
656
656
657 elif _vcs_kind == 'repo_branch_protected':
657 elif _vcs_kind == 'repo_branch_protected':
658 # Get custom repo-branch-protected status code if present.
658 # Get custom repo-branch-protected status code if present.
659 return HTTPRepoBranchProtected(title=exception.message)
659 return HTTPRepoBranchProtected(title=exception.message)
660
660
661 exc_info = request.exc_info
661 exc_info = request.exc_info
662 store_exception(id(exc_info), exc_info)
662 store_exception(id(exc_info), exc_info)
663
663
664 traceback_info = 'unavailable'
664 traceback_info = 'unavailable'
665 if request.exc_info:
665 if request.exc_info:
666 exc_type, exc_value, exc_tb = request.exc_info
666 exc_type, exc_value, exc_tb = request.exc_info
667 traceback_info = ''.join(traceback.format_exception(exc_type, exc_value, exc_tb))
667 traceback_info = ''.join(traceback.format_exception(exc_type, exc_value, exc_tb))
668
668
669 log.error(
669 log.error(
670 'error occurred handling this request for path: %s, \n tb: %s',
670 'error occurred handling this request for path: %s, \n tb: %s',
671 request.path, traceback_info)
671 request.path, traceback_info)
672 raise exception
672 raise exception
673
673
674
674
675 class ResponseFilter(object):
675 class ResponseFilter(object):
676
676
677 def __init__(self, start_response):
677 def __init__(self, start_response):
678 self._start_response = start_response
678 self._start_response = start_response
679
679
680 def __call__(self, status, response_headers, exc_info=None):
680 def __call__(self, status, response_headers, exc_info=None):
681 headers = tuple(
681 headers = tuple(
682 (h, v) for h, v in response_headers
682 (h, v) for h, v in response_headers
683 if not wsgiref.util.is_hop_by_hop(h))
683 if not wsgiref.util.is_hop_by_hop(h))
684 return self._start_response(status, headers, exc_info)
684 return self._start_response(status, headers, exc_info)
685
685
686
686
687 def main(global_config, **settings):
687 def main(global_config, **settings):
688 if MercurialFactory:
688 if MercurialFactory:
689 hgpatches.patch_largefiles_capabilities()
689 hgpatches.patch_largefiles_capabilities()
690 hgpatches.patch_subrepo_type_mapping()
690 hgpatches.patch_subrepo_type_mapping()
691
691
692 app = HTTPApplication(settings=settings, global_config=global_config)
692 app = HTTPApplication(settings=settings, global_config=global_config)
693 return app.wsgi_app()
693 return app.wsgi_app()
@@ -1,169 +1,167 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-2020 RhodeCode GmbH
4 # Copyright (C) 2014-2020 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 os
21 import os
22 import time
22 import time
23 import datetime
23 import datetime
24 import msgpack
24 import msgpack
25 import logging
25 import logging
26 import traceback
26 import traceback
27 import tempfile
27 import tempfile
28
28
29 from pyramid import compat
30
31 log = logging.getLogger(__name__)
29 log = logging.getLogger(__name__)
32
30
33 # NOTE: Any changes should be synced with exc_tracking at rhodecode.lib.exc_tracking
31 # NOTE: Any changes should be synced with exc_tracking at rhodecode.lib.exc_tracking
34 global_prefix = 'vcsserver'
32 global_prefix = 'vcsserver'
35 exc_store_dir_name = 'rc_exception_store_v1'
33 exc_store_dir_name = 'rc_exception_store_v1'
36
34
37
35
38 def exc_serialize(exc_id, tb, exc_type):
36 def exc_serialize(exc_id, tb, exc_type):
39
37
40 data = {
38 data = {
41 'version': 'v1',
39 'version': 'v1',
42 'exc_id': exc_id,
40 'exc_id': exc_id,
43 'exc_utc_date': datetime.datetime.utcnow().isoformat(),
41 'exc_utc_date': datetime.datetime.utcnow().isoformat(),
44 'exc_timestamp': repr(time.time()),
42 'exc_timestamp': repr(time.time()),
45 'exc_message': tb,
43 'exc_message': tb,
46 'exc_type': exc_type,
44 'exc_type': exc_type,
47 }
45 }
48 return msgpack.packb(data), data
46 return msgpack.packb(data), data
49
47
50
48
51 def exc_unserialize(tb):
49 def exc_unserialize(tb):
52 return msgpack.unpackb(tb)
50 return msgpack.unpackb(tb)
53
51
54
52
55 def get_exc_store():
53 def get_exc_store():
56 """
54 """
57 Get and create exception store if it's not existing
55 Get and create exception store if it's not existing
58 """
56 """
59 import vcsserver as app
57 import vcsserver as app
60
58
61 exc_store_dir = app.CONFIG.get('exception_tracker.store_path', '') or tempfile.gettempdir()
59 exc_store_dir = app.CONFIG.get('exception_tracker.store_path', '') or tempfile.gettempdir()
62 _exc_store_path = os.path.join(exc_store_dir, exc_store_dir_name)
60 _exc_store_path = os.path.join(exc_store_dir, exc_store_dir_name)
63
61
64 _exc_store_path = os.path.abspath(_exc_store_path)
62 _exc_store_path = os.path.abspath(_exc_store_path)
65 if not os.path.isdir(_exc_store_path):
63 if not os.path.isdir(_exc_store_path):
66 os.makedirs(_exc_store_path)
64 os.makedirs(_exc_store_path)
67 log.debug('Initializing exceptions store at %s', _exc_store_path)
65 log.debug('Initializing exceptions store at %s', _exc_store_path)
68 return _exc_store_path
66 return _exc_store_path
69
67
70
68
71 def _store_exception(exc_id, exc_info, prefix):
69 def _store_exception(exc_id, exc_info, prefix):
72 exc_type, exc_value, exc_traceback = exc_info
70 exc_type, exc_value, exc_traceback = exc_info
73
71
74 tb = ''.join(traceback.format_exception(
72 tb = ''.join(traceback.format_exception(
75 exc_type, exc_value, exc_traceback, None))
73 exc_type, exc_value, exc_traceback, None))
76
74
77 detailed_tb = getattr(exc_value, '_org_exc_tb', None)
75 detailed_tb = getattr(exc_value, '_org_exc_tb', None)
78
76
79 if detailed_tb:
77 if detailed_tb:
80 if isinstance(detailed_tb, compat.string_types):
78 if isinstance(detailed_tb, str):
81 remote_tb = [detailed_tb]
79 remote_tb = [detailed_tb]
82
80
83 tb += (
81 tb += (
84 '\n+++ BEG SOURCE EXCEPTION +++\n\n'
82 '\n+++ BEG SOURCE EXCEPTION +++\n\n'
85 '{}\n'
83 '{}\n'
86 '+++ END SOURCE EXCEPTION +++\n'
84 '+++ END SOURCE EXCEPTION +++\n'
87 ''.format('\n'.join(remote_tb))
85 ''.format('\n'.join(remote_tb))
88 )
86 )
89
87
90 # Avoid that remote_tb also appears in the frame
88 # Avoid that remote_tb also appears in the frame
91 del remote_tb
89 del remote_tb
92
90
93 exc_type_name = exc_type.__name__
91 exc_type_name = exc_type.__name__
94 exc_store_path = get_exc_store()
92 exc_store_path = get_exc_store()
95 exc_data, org_data = exc_serialize(exc_id, tb, exc_type_name)
93 exc_data, org_data = exc_serialize(exc_id, tb, exc_type_name)
96 exc_pref_id = '{}_{}_{}'.format(exc_id, prefix, org_data['exc_timestamp'])
94 exc_pref_id = '{}_{}_{}'.format(exc_id, prefix, org_data['exc_timestamp'])
97 if not os.path.isdir(exc_store_path):
95 if not os.path.isdir(exc_store_path):
98 os.makedirs(exc_store_path)
96 os.makedirs(exc_store_path)
99 stored_exc_path = os.path.join(exc_store_path, exc_pref_id)
97 stored_exc_path = os.path.join(exc_store_path, exc_pref_id)
100 with open(stored_exc_path, 'wb') as f:
98 with open(stored_exc_path, 'wb') as f:
101 f.write(exc_data)
99 f.write(exc_data)
102 log.debug('Stored generated exception %s as: %s', exc_id, stored_exc_path)
100 log.debug('Stored generated exception %s as: %s', exc_id, stored_exc_path)
103
101
104
102
105 def store_exception(exc_id, exc_info, prefix=global_prefix):
103 def store_exception(exc_id, exc_info, prefix=global_prefix):
106 """
104 """
107 Example usage::
105 Example usage::
108
106
109 exc_info = sys.exc_info()
107 exc_info = sys.exc_info()
110 store_exception(id(exc_info), exc_info)
108 store_exception(id(exc_info), exc_info)
111 """
109 """
112
110
113 try:
111 try:
114 _store_exception(exc_id=exc_id, exc_info=exc_info, prefix=prefix)
112 _store_exception(exc_id=exc_id, exc_info=exc_info, prefix=prefix)
115 except Exception:
113 except Exception:
116 log.exception('Failed to store exception `%s` information', exc_id)
114 log.exception('Failed to store exception `%s` information', exc_id)
117 # there's no way this can fail, it will crash server badly if it does.
115 # there's no way this can fail, it will crash server badly if it does.
118 pass
116 pass
119
117
120
118
121 def _find_exc_file(exc_id, prefix=global_prefix):
119 def _find_exc_file(exc_id, prefix=global_prefix):
122 exc_store_path = get_exc_store()
120 exc_store_path = get_exc_store()
123 if prefix:
121 if prefix:
124 exc_id = '{}_{}'.format(exc_id, prefix)
122 exc_id = '{}_{}'.format(exc_id, prefix)
125 else:
123 else:
126 # search without a prefix
124 # search without a prefix
127 exc_id = '{}'.format(exc_id)
125 exc_id = '{}'.format(exc_id)
128
126
129 # we need to search the store for such start pattern as above
127 # we need to search the store for such start pattern as above
130 for fname in os.listdir(exc_store_path):
128 for fname in os.listdir(exc_store_path):
131 if fname.startswith(exc_id):
129 if fname.startswith(exc_id):
132 exc_id = os.path.join(exc_store_path, fname)
130 exc_id = os.path.join(exc_store_path, fname)
133 break
131 break
134 continue
132 continue
135 else:
133 else:
136 exc_id = None
134 exc_id = None
137
135
138 return exc_id
136 return exc_id
139
137
140
138
141 def _read_exception(exc_id, prefix):
139 def _read_exception(exc_id, prefix):
142 exc_id_file_path = _find_exc_file(exc_id=exc_id, prefix=prefix)
140 exc_id_file_path = _find_exc_file(exc_id=exc_id, prefix=prefix)
143 if exc_id_file_path:
141 if exc_id_file_path:
144 with open(exc_id_file_path, 'rb') as f:
142 with open(exc_id_file_path, 'rb') as f:
145 return exc_unserialize(f.read())
143 return exc_unserialize(f.read())
146 else:
144 else:
147 log.debug('Exception File `%s` not found', exc_id_file_path)
145 log.debug('Exception File `%s` not found', exc_id_file_path)
148 return None
146 return None
149
147
150
148
151 def read_exception(exc_id, prefix=global_prefix):
149 def read_exception(exc_id, prefix=global_prefix):
152 try:
150 try:
153 return _read_exception(exc_id=exc_id, prefix=prefix)
151 return _read_exception(exc_id=exc_id, prefix=prefix)
154 except Exception:
152 except Exception:
155 log.exception('Failed to read exception `%s` information', exc_id)
153 log.exception('Failed to read exception `%s` information', exc_id)
156 # there's no way this can fail, it will crash server badly if it does.
154 # there's no way this can fail, it will crash server badly if it does.
157 return None
155 return None
158
156
159
157
160 def delete_exception(exc_id, prefix=global_prefix):
158 def delete_exception(exc_id, prefix=global_prefix):
161 try:
159 try:
162 exc_id_file_path = _find_exc_file(exc_id, prefix=prefix)
160 exc_id_file_path = _find_exc_file(exc_id, prefix=prefix)
163 if exc_id_file_path:
161 if exc_id_file_path:
164 os.remove(exc_id_file_path)
162 os.remove(exc_id_file_path)
165
163
166 except Exception:
164 except Exception:
167 log.exception('Failed to remove exception `%s` information', exc_id)
165 log.exception('Failed to remove exception `%s` information', exc_id)
168 # there's no way this can fail, it will crash server badly if it does.
166 # there's no way this can fail, it will crash server badly if it does.
169 pass
167 pass
@@ -1,253 +1,253 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 errno
19 import errno
20 import logging
20 import logging
21
21
22 import msgpack
22 import msgpack
23 import redis
23 import redis
24
24
25 from dogpile.cache.api import CachedValue
25 from dogpile.cache.api import CachedValue
26 from dogpile.cache.backends import memory as memory_backend
26 from dogpile.cache.backends import memory as memory_backend
27 from dogpile.cache.backends import file as file_backend
27 from dogpile.cache.backends import file as file_backend
28 from dogpile.cache.backends import redis as redis_backend
28 from dogpile.cache.backends import redis as redis_backend
29 from dogpile.cache.backends.file import NO_VALUE, compat, FileLock
29 from dogpile.cache.backends.file import NO_VALUE, FileLock
30 from dogpile.cache.util import memoized_property
30 from dogpile.cache.util import memoized_property
31
31
32 from vcsserver.lib.memory_lru_dict import LRUDict, LRUDictDebug
32 from vcsserver.lib.memory_lru_dict import LRUDict, LRUDictDebug
33
33
34
34
35 _default_max_size = 1024
35 _default_max_size = 1024
36
36
37 log = logging.getLogger(__name__)
37 log = logging.getLogger(__name__)
38
38
39
39
40 class LRUMemoryBackend(memory_backend.MemoryBackend):
40 class LRUMemoryBackend(memory_backend.MemoryBackend):
41 key_prefix = 'lru_mem_backend'
41 key_prefix = 'lru_mem_backend'
42 pickle_values = False
42 pickle_values = False
43
43
44 def __init__(self, arguments):
44 def __init__(self, arguments):
45 max_size = arguments.pop('max_size', _default_max_size)
45 max_size = arguments.pop('max_size', _default_max_size)
46
46
47 LRUDictClass = LRUDict
47 LRUDictClass = LRUDict
48 if arguments.pop('log_key_count', None):
48 if arguments.pop('log_key_count', None):
49 LRUDictClass = LRUDictDebug
49 LRUDictClass = LRUDictDebug
50
50
51 arguments['cache_dict'] = LRUDictClass(max_size)
51 arguments['cache_dict'] = LRUDictClass(max_size)
52 super(LRUMemoryBackend, self).__init__(arguments)
52 super(LRUMemoryBackend, self).__init__(arguments)
53
53
54 def delete(self, key):
54 def delete(self, key):
55 try:
55 try:
56 del self._cache[key]
56 del self._cache[key]
57 except KeyError:
57 except KeyError:
58 # we don't care if key isn't there at deletion
58 # we don't care if key isn't there at deletion
59 pass
59 pass
60
60
61 def delete_multi(self, keys):
61 def delete_multi(self, keys):
62 for key in keys:
62 for key in keys:
63 self.delete(key)
63 self.delete(key)
64
64
65
65
66 class PickleSerializer(object):
66 class PickleSerializer(object):
67
67
68 def _dumps(self, value, safe=False):
68 def _dumps(self, value, safe=False):
69 try:
69 try:
70 return compat.pickle.dumps(value)
70 return pickle.dumps(value)
71 except Exception:
71 except Exception:
72 if safe:
72 if safe:
73 return NO_VALUE
73 return NO_VALUE
74 else:
74 else:
75 raise
75 raise
76
76
77 def _loads(self, value, safe=True):
77 def _loads(self, value, safe=True):
78 try:
78 try:
79 return compat.pickle.loads(value)
79 return pickle.loads(value)
80 except Exception:
80 except Exception:
81 if safe:
81 if safe:
82 return NO_VALUE
82 return NO_VALUE
83 else:
83 else:
84 raise
84 raise
85
85
86
86
87 class MsgPackSerializer(object):
87 class MsgPackSerializer(object):
88
88
89 def _dumps(self, value, safe=False):
89 def _dumps(self, value, safe=False):
90 try:
90 try:
91 return msgpack.packb(value)
91 return msgpack.packb(value)
92 except Exception:
92 except Exception:
93 if safe:
93 if safe:
94 return NO_VALUE
94 return NO_VALUE
95 else:
95 else:
96 raise
96 raise
97
97
98 def _loads(self, value, safe=True):
98 def _loads(self, value, safe=True):
99 """
99 """
100 pickle maintained the `CachedValue` wrapper of the tuple
100 pickle maintained the `CachedValue` wrapper of the tuple
101 msgpack does not, so it must be added back in.
101 msgpack does not, so it must be added back in.
102 """
102 """
103 try:
103 try:
104 value = msgpack.unpackb(value, use_list=False)
104 value = msgpack.unpackb(value, use_list=False)
105 return CachedValue(*value)
105 return CachedValue(*value)
106 except Exception:
106 except Exception:
107 if safe:
107 if safe:
108 return NO_VALUE
108 return NO_VALUE
109 else:
109 else:
110 raise
110 raise
111
111
112
112
113 import fcntl
113 import fcntl
114 flock_org = fcntl.flock
114 flock_org = fcntl.flock
115
115
116
116
117 class CustomLockFactory(FileLock):
117 class CustomLockFactory(FileLock):
118
118
119 pass
119 pass
120
120
121
121
122 class FileNamespaceBackend(PickleSerializer, file_backend.DBMBackend):
122 class FileNamespaceBackend(PickleSerializer, file_backend.DBMBackend):
123 key_prefix = 'file_backend'
123 key_prefix = 'file_backend'
124
124
125 def __init__(self, arguments):
125 def __init__(self, arguments):
126 arguments['lock_factory'] = CustomLockFactory
126 arguments['lock_factory'] = CustomLockFactory
127 super(FileNamespaceBackend, self).__init__(arguments)
127 super(FileNamespaceBackend, self).__init__(arguments)
128
128
129 def __repr__(self):
129 def __repr__(self):
130 return '{} `{}`'.format(self.__class__, self.filename)
130 return '{} `{}`'.format(self.__class__, self.filename)
131
131
132 def list_keys(self, prefix=''):
132 def list_keys(self, prefix=''):
133 prefix = '{}:{}'.format(self.key_prefix, prefix)
133 prefix = '{}:{}'.format(self.key_prefix, prefix)
134
134
135 def cond(v):
135 def cond(v):
136 if not prefix:
136 if not prefix:
137 return True
137 return True
138
138
139 if v.startswith(prefix):
139 if v.startswith(prefix):
140 return True
140 return True
141 return False
141 return False
142
142
143 with self._dbm_file(True) as dbm:
143 with self._dbm_file(True) as dbm:
144
144
145 return filter(cond, dbm.keys())
145 return filter(cond, dbm.keys())
146
146
147 def get_store(self):
147 def get_store(self):
148 return self.filename
148 return self.filename
149
149
150 def get(self, key):
150 def get(self, key):
151 with self._dbm_file(False) as dbm:
151 with self._dbm_file(False) as dbm:
152 if hasattr(dbm, 'get'):
152 if hasattr(dbm, 'get'):
153 value = dbm.get(key, NO_VALUE)
153 value = dbm.get(key, NO_VALUE)
154 else:
154 else:
155 # gdbm objects lack a .get method
155 # gdbm objects lack a .get method
156 try:
156 try:
157 value = dbm[key]
157 value = dbm[key]
158 except KeyError:
158 except KeyError:
159 value = NO_VALUE
159 value = NO_VALUE
160 if value is not NO_VALUE:
160 if value is not NO_VALUE:
161 value = self._loads(value)
161 value = self._loads(value)
162 return value
162 return value
163
163
164 def set(self, key, value):
164 def set(self, key, value):
165 with self._dbm_file(True) as dbm:
165 with self._dbm_file(True) as dbm:
166 dbm[key] = self._dumps(value)
166 dbm[key] = self._dumps(value)
167
167
168 def set_multi(self, mapping):
168 def set_multi(self, mapping):
169 with self._dbm_file(True) as dbm:
169 with self._dbm_file(True) as dbm:
170 for key, value in mapping.items():
170 for key, value in mapping.items():
171 dbm[key] = self._dumps(value)
171 dbm[key] = self._dumps(value)
172
172
173
173
174 class BaseRedisBackend(redis_backend.RedisBackend):
174 class BaseRedisBackend(redis_backend.RedisBackend):
175
175
176 def _create_client(self):
176 def _create_client(self):
177 args = {}
177 args = {}
178
178
179 if self.url is not None:
179 if self.url is not None:
180 args.update(url=self.url)
180 args.update(url=self.url)
181
181
182 else:
182 else:
183 args.update(
183 args.update(
184 host=self.host, password=self.password,
184 host=self.host, password=self.password,
185 port=self.port, db=self.db
185 port=self.port, db=self.db
186 )
186 )
187
187
188 connection_pool = redis.ConnectionPool(**args)
188 connection_pool = redis.ConnectionPool(**args)
189
189
190 return redis.StrictRedis(connection_pool=connection_pool)
190 return redis.StrictRedis(connection_pool=connection_pool)
191
191
192 def list_keys(self, prefix=''):
192 def list_keys(self, prefix=''):
193 prefix = '{}:{}*'.format(self.key_prefix, prefix)
193 prefix = '{}:{}*'.format(self.key_prefix, prefix)
194 return self.client.keys(prefix)
194 return self.client.keys(prefix)
195
195
196 def get_store(self):
196 def get_store(self):
197 return self.client.connection_pool
197 return self.client.connection_pool
198
198
199 def get(self, key):
199 def get(self, key):
200 value = self.client.get(key)
200 value = self.client.get(key)
201 if value is None:
201 if value is None:
202 return NO_VALUE
202 return NO_VALUE
203 return self._loads(value)
203 return self._loads(value)
204
204
205 def get_multi(self, keys):
205 def get_multi(self, keys):
206 if not keys:
206 if not keys:
207 return []
207 return []
208 values = self.client.mget(keys)
208 values = self.client.mget(keys)
209 loads = self._loads
209 loads = self._loads
210 return [
210 return [
211 loads(v) if v is not None else NO_VALUE
211 loads(v) if v is not None else NO_VALUE
212 for v in values]
212 for v in values]
213
213
214 def set(self, key, value):
214 def set(self, key, value):
215 if self.redis_expiration_time:
215 if self.redis_expiration_time:
216 self.client.setex(key, self.redis_expiration_time,
216 self.client.setex(key, self.redis_expiration_time,
217 self._dumps(value))
217 self._dumps(value))
218 else:
218 else:
219 self.client.set(key, self._dumps(value))
219 self.client.set(key, self._dumps(value))
220
220
221 def set_multi(self, mapping):
221 def set_multi(self, mapping):
222 dumps = self._dumps
222 dumps = self._dumps
223 mapping = dict(
223 mapping = dict(
224 (k, dumps(v))
224 (k, dumps(v))
225 for k, v in mapping.items()
225 for k, v in mapping.items()
226 )
226 )
227
227
228 if not self.redis_expiration_time:
228 if not self.redis_expiration_time:
229 self.client.mset(mapping)
229 self.client.mset(mapping)
230 else:
230 else:
231 pipe = self.client.pipeline()
231 pipe = self.client.pipeline()
232 for key, value in mapping.items():
232 for key, value in mapping.items():
233 pipe.setex(key, self.redis_expiration_time, value)
233 pipe.setex(key, self.redis_expiration_time, value)
234 pipe.execute()
234 pipe.execute()
235
235
236 def get_mutex(self, key):
236 def get_mutex(self, key):
237 u = redis_backend.u
237 u = redis_backend.u
238 if self.distributed_lock:
238 if self.distributed_lock:
239 lock_key = u('_lock_{0}').format(key)
239 lock_key = u('_lock_{0}').format(key)
240 log.debug('Trying to acquire Redis lock for key %s', lock_key)
240 log.debug('Trying to acquire Redis lock for key %s', lock_key)
241 return self.client.lock(lock_key, self.lock_timeout, self.lock_sleep)
241 return self.client.lock(lock_key, self.lock_timeout, self.lock_sleep)
242 else:
242 else:
243 return None
243 return None
244
244
245
245
246 class RedisPickleBackend(PickleSerializer, BaseRedisBackend):
246 class RedisPickleBackend(PickleSerializer, BaseRedisBackend):
247 key_prefix = 'redis_pickle_backend'
247 key_prefix = 'redis_pickle_backend'
248 pass
248 pass
249
249
250
250
251 class RedisMsgPackBackend(MsgPackSerializer, BaseRedisBackend):
251 class RedisMsgPackBackend(MsgPackSerializer, BaseRedisBackend):
252 key_prefix = 'redis_msgpack_backend'
252 key_prefix = 'redis_msgpack_backend'
253 pass
253 pass
General Comments 0
You need to be logged in to leave comments. Login now