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