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