##// END OF EJS Templates
exceptions: show original remote traceback, and more info if debug is enabled.
marcink -
r3369:ba0393a0 default
parent child Browse files
Show More
@@ -1,311 +1,311 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2016-2019 RhodeCode GmbH
3 # Copyright (C) 2016-2019 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
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 Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 """
21 """
22 Client for the VCSServer implemented based on HTTP.
22 Client for the VCSServer implemented based on HTTP.
23 """
23 """
24
24
25 import copy
25 import copy
26 import logging
26 import logging
27 import threading
27 import threading
28 import urllib2
28 import urllib2
29 import urlparse
29 import urlparse
30 import uuid
30 import uuid
31 import traceback
31 import traceback
32
32
33 import pycurl
33 import pycurl
34 import msgpack
34 import msgpack
35 import requests
35 import requests
36 from requests.packages.urllib3.util.retry import Retry
36 from requests.packages.urllib3.util.retry import Retry
37
37
38 import rhodecode
38 import rhodecode
39 from rhodecode.lib.system_info import get_cert_path
39 from rhodecode.lib.system_info import get_cert_path
40 from rhodecode.lib.vcs import exceptions, CurlSession
40 from rhodecode.lib.vcs import exceptions, CurlSession
41
41
42
42
43 log = logging.getLogger(__name__)
43 log = logging.getLogger(__name__)
44
44
45
45
46 # TODO: mikhail: Keep it in sync with vcsserver's
46 # TODO: mikhail: Keep it in sync with vcsserver's
47 # HTTPApplication.ALLOWED_EXCEPTIONS
47 # HTTPApplication.ALLOWED_EXCEPTIONS
48 EXCEPTIONS_MAP = {
48 EXCEPTIONS_MAP = {
49 'KeyError': KeyError,
49 'KeyError': KeyError,
50 'URLError': urllib2.URLError,
50 'URLError': urllib2.URLError,
51 }
51 }
52
52
53
53
54 class RepoMaker(object):
54 class RepoMaker(object):
55
55
56 def __init__(self, server_and_port, backend_endpoint, backend_type, session_factory):
56 def __init__(self, server_and_port, backend_endpoint, backend_type, session_factory):
57 self.url = urlparse.urljoin(
57 self.url = urlparse.urljoin(
58 'http://%s' % server_and_port, backend_endpoint)
58 'http://%s' % server_and_port, backend_endpoint)
59 self._session_factory = session_factory
59 self._session_factory = session_factory
60 self.backend_type = backend_type
60 self.backend_type = backend_type
61
61
62 def __call__(self, path, config, with_wire=None):
62 def __call__(self, path, config, with_wire=None):
63 log.debug('RepoMaker call on %s', path)
63 log.debug('RepoMaker call on %s', path)
64 return RemoteRepo(
64 return RemoteRepo(
65 path, config, self.url, self._session_factory(),
65 path, config, self.url, self._session_factory(),
66 with_wire=with_wire)
66 with_wire=with_wire)
67
67
68 def __getattr__(self, name):
68 def __getattr__(self, name):
69 def f(*args, **kwargs):
69 def f(*args, **kwargs):
70 return self._call(name, *args, **kwargs)
70 return self._call(name, *args, **kwargs)
71 return f
71 return f
72
72
73 @exceptions.map_vcs_exceptions
73 @exceptions.map_vcs_exceptions
74 def _call(self, name, *args, **kwargs):
74 def _call(self, name, *args, **kwargs):
75 payload = {
75 payload = {
76 'id': str(uuid.uuid4()),
76 'id': str(uuid.uuid4()),
77 'method': name,
77 'method': name,
78 'backend': self.backend_type,
78 'backend': self.backend_type,
79 'params': {'args': args, 'kwargs': kwargs}
79 'params': {'args': args, 'kwargs': kwargs}
80 }
80 }
81 return _remote_call(
81 return _remote_call(
82 self.url, payload, EXCEPTIONS_MAP, self._session_factory())
82 self.url, payload, EXCEPTIONS_MAP, self._session_factory())
83
83
84
84
85 class ServiceConnection(object):
85 class ServiceConnection(object):
86 def __init__(self, server_and_port, backend_endpoint, session_factory):
86 def __init__(self, server_and_port, backend_endpoint, session_factory):
87 self.url = urlparse.urljoin(
87 self.url = urlparse.urljoin(
88 'http://%s' % server_and_port, backend_endpoint)
88 'http://%s' % server_and_port, backend_endpoint)
89 self._session_factory = session_factory
89 self._session_factory = session_factory
90
90
91 def __getattr__(self, name):
91 def __getattr__(self, name):
92 def f(*args, **kwargs):
92 def f(*args, **kwargs):
93 return self._call(name, *args, **kwargs)
93 return self._call(name, *args, **kwargs)
94
94
95 return f
95 return f
96
96
97 @exceptions.map_vcs_exceptions
97 @exceptions.map_vcs_exceptions
98 def _call(self, name, *args, **kwargs):
98 def _call(self, name, *args, **kwargs):
99 payload = {
99 payload = {
100 'id': str(uuid.uuid4()),
100 'id': str(uuid.uuid4()),
101 'method': name,
101 'method': name,
102 'params': {'args': args, 'kwargs': kwargs}
102 'params': {'args': args, 'kwargs': kwargs}
103 }
103 }
104 return _remote_call(
104 return _remote_call(
105 self.url, payload, EXCEPTIONS_MAP, self._session_factory())
105 self.url, payload, EXCEPTIONS_MAP, self._session_factory())
106
106
107
107
108 class RemoteRepo(object):
108 class RemoteRepo(object):
109
109
110 def __init__(self, path, config, url, session, with_wire=None):
110 def __init__(self, path, config, url, session, with_wire=None):
111 self.url = url
111 self.url = url
112 self._session = session
112 self._session = session
113 self._wire = {
113 self._wire = {
114 "path": path,
114 "path": path,
115 "config": config,
115 "config": config,
116 "context": self._create_vcs_cache_context(),
116 "context": self._create_vcs_cache_context(),
117 }
117 }
118 if with_wire:
118 if with_wire:
119 self._wire.update(with_wire)
119 self._wire.update(with_wire)
120
120
121 # johbo: Trading complexity for performance. Avoiding the call to
121 # johbo: Trading complexity for performance. Avoiding the call to
122 # log.debug brings a few percent gain even if is is not active.
122 # log.debug brings a few percent gain even if is is not active.
123 if log.isEnabledFor(logging.DEBUG):
123 if log.isEnabledFor(logging.DEBUG):
124 self._call = self._call_with_logging
124 self._call = self._call_with_logging
125
125
126 self.cert_dir = get_cert_path(rhodecode.CONFIG.get('__file__'))
126 self.cert_dir = get_cert_path(rhodecode.CONFIG.get('__file__'))
127
127
128 def __getattr__(self, name):
128 def __getattr__(self, name):
129 def f(*args, **kwargs):
129 def f(*args, **kwargs):
130 return self._call(name, *args, **kwargs)
130 return self._call(name, *args, **kwargs)
131 return f
131 return f
132
132
133 @exceptions.map_vcs_exceptions
133 @exceptions.map_vcs_exceptions
134 def _call(self, name, *args, **kwargs):
134 def _call(self, name, *args, **kwargs):
135 # TODO: oliver: This is currently necessary pre-call since the
135 # TODO: oliver: This is currently necessary pre-call since the
136 # config object is being changed for hooking scenarios
136 # config object is being changed for hooking scenarios
137 wire = copy.deepcopy(self._wire)
137 wire = copy.deepcopy(self._wire)
138 wire["config"] = wire["config"].serialize()
138 wire["config"] = wire["config"].serialize()
139
139
140 wire["config"].append(('vcs', 'ssl_dir', self.cert_dir))
140 wire["config"].append(('vcs', 'ssl_dir', self.cert_dir))
141 payload = {
141 payload = {
142 'id': str(uuid.uuid4()),
142 'id': str(uuid.uuid4()),
143 'method': name,
143 'method': name,
144 'params': {'wire': wire, 'args': args, 'kwargs': kwargs}
144 'params': {'wire': wire, 'args': args, 'kwargs': kwargs}
145 }
145 }
146 return _remote_call(self.url, payload, EXCEPTIONS_MAP, self._session)
146 return _remote_call(self.url, payload, EXCEPTIONS_MAP, self._session)
147
147
148 def _call_with_logging(self, name, *args, **kwargs):
148 def _call_with_logging(self, name, *args, **kwargs):
149 context_uid = self._wire.get('context')
149 context_uid = self._wire.get('context')
150 log.debug('Calling %s@%s with args:%r. wire_context: %s',
150 log.debug('Calling %s@%s with args:%r. wire_context: %s',
151 self.url, name, args, context_uid)
151 self.url, name, args, context_uid)
152 return RemoteRepo._call(self, name, *args, **kwargs)
152 return RemoteRepo._call(self, name, *args, **kwargs)
153
153
154 def __getitem__(self, key):
154 def __getitem__(self, key):
155 return self.revision(key)
155 return self.revision(key)
156
156
157 def _create_vcs_cache_context(self):
157 def _create_vcs_cache_context(self):
158 """
158 """
159 Creates a unique string which is passed to the VCSServer on every
159 Creates a unique string which is passed to the VCSServer on every
160 remote call. It is used as cache key in the VCSServer.
160 remote call. It is used as cache key in the VCSServer.
161 """
161 """
162 return str(uuid.uuid4())
162 return str(uuid.uuid4())
163
163
164 def invalidate_vcs_cache(self):
164 def invalidate_vcs_cache(self):
165 """
165 """
166 This invalidates the context which is sent to the VCSServer on every
166 This invalidates the context which is sent to the VCSServer on every
167 call to a remote method. It forces the VCSServer to create a fresh
167 call to a remote method. It forces the VCSServer to create a fresh
168 repository instance on the next call to a remote method.
168 repository instance on the next call to a remote method.
169 """
169 """
170 self._wire['context'] = self._create_vcs_cache_context()
170 self._wire['context'] = self._create_vcs_cache_context()
171
171
172
172
173 class RemoteObject(object):
173 class RemoteObject(object):
174
174
175 def __init__(self, url, session):
175 def __init__(self, url, session):
176 self._url = url
176 self._url = url
177 self._session = session
177 self._session = session
178
178
179 # johbo: Trading complexity for performance. Avoiding the call to
179 # johbo: Trading complexity for performance. Avoiding the call to
180 # log.debug brings a few percent gain even if is is not active.
180 # log.debug brings a few percent gain even if is is not active.
181 if log.isEnabledFor(logging.DEBUG):
181 if log.isEnabledFor(logging.DEBUG):
182 self._call = self._call_with_logging
182 self._call = self._call_with_logging
183
183
184 def __getattr__(self, name):
184 def __getattr__(self, name):
185 def f(*args, **kwargs):
185 def f(*args, **kwargs):
186 return self._call(name, *args, **kwargs)
186 return self._call(name, *args, **kwargs)
187 return f
187 return f
188
188
189 @exceptions.map_vcs_exceptions
189 @exceptions.map_vcs_exceptions
190 def _call(self, name, *args, **kwargs):
190 def _call(self, name, *args, **kwargs):
191 payload = {
191 payload = {
192 'id': str(uuid.uuid4()),
192 'id': str(uuid.uuid4()),
193 'method': name,
193 'method': name,
194 'params': {'args': args, 'kwargs': kwargs}
194 'params': {'args': args, 'kwargs': kwargs}
195 }
195 }
196 return _remote_call(self._url, payload, EXCEPTIONS_MAP, self._session)
196 return _remote_call(self._url, payload, EXCEPTIONS_MAP, self._session)
197
197
198 def _call_with_logging(self, name, *args, **kwargs):
198 def _call_with_logging(self, name, *args, **kwargs):
199 log.debug('Calling %s@%s', self._url, name)
199 log.debug('Calling %s@%s', self._url, name)
200 return RemoteObject._call(self, name, *args, **kwargs)
200 return RemoteObject._call(self, name, *args, **kwargs)
201
201
202
202
203 def _remote_call(url, payload, exceptions_map, session):
203 def _remote_call(url, payload, exceptions_map, session):
204 try:
204 try:
205 response = session.post(url, data=msgpack.packb(payload))
205 response = session.post(url, data=msgpack.packb(payload))
206 except pycurl.error as e:
206 except pycurl.error as e:
207 msg = '{}. \npycurl traceback: {}'.format(e, traceback.format_exc())
207 msg = '{}. \npycurl traceback: {}'.format(e, traceback.format_exc())
208 raise exceptions.HttpVCSCommunicationError(msg)
208 raise exceptions.HttpVCSCommunicationError(msg)
209 except Exception as e:
209 except Exception as e:
210 message = getattr(e, 'message', '')
210 message = getattr(e, 'message', '')
211 if 'Failed to connect' in message:
211 if 'Failed to connect' in message:
212 # gevent doesn't return proper pycurl errors
212 # gevent doesn't return proper pycurl errors
213 raise exceptions.HttpVCSCommunicationError(e)
213 raise exceptions.HttpVCSCommunicationError(e)
214 else:
214 else:
215 raise
215 raise
216
216
217 if response.status_code >= 400:
217 if response.status_code >= 400:
218 log.error('Call to %s returned non 200 HTTP code: %s',
218 log.error('Call to %s returned non 200 HTTP code: %s',
219 url, response.status_code)
219 url, response.status_code)
220 raise exceptions.HttpVCSCommunicationError(repr(response.content))
220 raise exceptions.HttpVCSCommunicationError(repr(response.content))
221
221
222 try:
222 try:
223 response = msgpack.unpackb(response.content)
223 response = msgpack.unpackb(response.content)
224 except Exception:
224 except Exception:
225 log.exception('Failed to decode response %r', response.content)
225 log.exception('Failed to decode response %r', response.content)
226 raise
226 raise
227
227
228 error = response.get('error')
228 error = response.get('error')
229 if error:
229 if error:
230 type_ = error.get('type', 'Exception')
230 type_ = error.get('type', 'Exception')
231 exc = exceptions_map.get(type_, Exception)
231 exc = exceptions_map.get(type_, Exception)
232 exc = exc(error.get('message'))
232 exc = exc(error.get('message'))
233 try:
233 try:
234 exc._vcs_kind = error['_vcs_kind']
234 exc._vcs_kind = error['_vcs_kind']
235 except KeyError:
235 except KeyError:
236 pass
236 pass
237
237
238 try:
238 try:
239 exc._vcs_server_traceback = error['traceback']
239 exc._vcs_server_traceback = error['traceback']
240 exc._vcs_server_org_exc_name = error['org_exc']
241 exc._vcs_server_org_exc_tb = error['org_exc_tb']
240 except KeyError:
242 except KeyError:
241 pass
243 pass
242
244
243 raise exc
245 raise exc
244 return response.get('result')
246 return response.get('result')
245
247
246
248
247 class VcsHttpProxy(object):
249 class VcsHttpProxy(object):
248
250
249 CHUNK_SIZE = 16384
251 CHUNK_SIZE = 16384
250
252
251 def __init__(self, server_and_port, backend_endpoint):
253 def __init__(self, server_and_port, backend_endpoint):
252
253
254 retries = Retry(total=5, connect=None, read=None, redirect=None)
254 retries = Retry(total=5, connect=None, read=None, redirect=None)
255
255
256 adapter = requests.adapters.HTTPAdapter(max_retries=retries)
256 adapter = requests.adapters.HTTPAdapter(max_retries=retries)
257 self.base_url = urlparse.urljoin(
257 self.base_url = urlparse.urljoin(
258 'http://%s' % server_and_port, backend_endpoint)
258 'http://%s' % server_and_port, backend_endpoint)
259 self.session = requests.Session()
259 self.session = requests.Session()
260 self.session.mount('http://', adapter)
260 self.session.mount('http://', adapter)
261
261
262 def handle(self, environment, input_data, *args, **kwargs):
262 def handle(self, environment, input_data, *args, **kwargs):
263 data = {
263 data = {
264 'environment': environment,
264 'environment': environment,
265 'input_data': input_data,
265 'input_data': input_data,
266 'args': args,
266 'args': args,
267 'kwargs': kwargs
267 'kwargs': kwargs
268 }
268 }
269 result = self.session.post(
269 result = self.session.post(
270 self.base_url, msgpack.packb(data), stream=True)
270 self.base_url, msgpack.packb(data), stream=True)
271 return self._get_result(result)
271 return self._get_result(result)
272
272
273 def _deserialize_and_raise(self, error):
273 def _deserialize_and_raise(self, error):
274 exception = Exception(error['message'])
274 exception = Exception(error['message'])
275 try:
275 try:
276 exception._vcs_kind = error['_vcs_kind']
276 exception._vcs_kind = error['_vcs_kind']
277 except KeyError:
277 except KeyError:
278 pass
278 pass
279 raise exception
279 raise exception
280
280
281 def _iterate(self, result):
281 def _iterate(self, result):
282 unpacker = msgpack.Unpacker()
282 unpacker = msgpack.Unpacker()
283 for line in result.iter_content(chunk_size=self.CHUNK_SIZE):
283 for line in result.iter_content(chunk_size=self.CHUNK_SIZE):
284 unpacker.feed(line)
284 unpacker.feed(line)
285 for chunk in unpacker:
285 for chunk in unpacker:
286 yield chunk
286 yield chunk
287
287
288 def _get_result(self, result):
288 def _get_result(self, result):
289 iterator = self._iterate(result)
289 iterator = self._iterate(result)
290 error = iterator.next()
290 error = iterator.next()
291 if error:
291 if error:
292 self._deserialize_and_raise(error)
292 self._deserialize_and_raise(error)
293
293
294 status = iterator.next()
294 status = iterator.next()
295 headers = iterator.next()
295 headers = iterator.next()
296
296
297 return iterator, status, headers
297 return iterator, status, headers
298
298
299
299
300 class ThreadlocalSessionFactory(object):
300 class ThreadlocalSessionFactory(object):
301 """
301 """
302 Creates one CurlSession per thread on demand.
302 Creates one CurlSession per thread on demand.
303 """
303 """
304
304
305 def __init__(self):
305 def __init__(self):
306 self._thread_local = threading.local()
306 self._thread_local = threading.local()
307
307
308 def __call__(self):
308 def __call__(self):
309 if not hasattr(self._thread_local, 'curl_session'):
309 if not hasattr(self._thread_local, 'curl_session'):
310 self._thread_local.curl_session = CurlSession()
310 self._thread_local.curl_session = CurlSession()
311 return self._thread_local.curl_session
311 return self._thread_local.curl_session
@@ -1,215 +1,225 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2014-2019 RhodeCode GmbH
3 # Copyright (C) 2014-2019 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
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 Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 """
21 """
22 Custom vcs exceptions module.
22 Custom vcs exceptions module.
23 """
23 """
24 import logging
24 import logging
25 import functools
25 import functools
26 import urllib2
26 import urllib2
27 import rhodecode
27
28
28 log = logging.getLogger(__name__)
29 log = logging.getLogger(__name__)
29
30
30
31
31 class VCSCommunicationError(Exception):
32 class VCSCommunicationError(Exception):
32 pass
33 pass
33
34
34
35
35 class HttpVCSCommunicationError(VCSCommunicationError):
36 class HttpVCSCommunicationError(VCSCommunicationError):
36 pass
37 pass
37
38
38
39
39 class VCSError(Exception):
40 class VCSError(Exception):
40 pass
41 pass
41
42
42
43
43 class RepositoryError(VCSError):
44 class RepositoryError(VCSError):
44 pass
45 pass
45
46
46
47
47 class RepositoryRequirementError(RepositoryError):
48 class RepositoryRequirementError(RepositoryError):
48 pass
49 pass
49
50
50
51
51 class VCSBackendNotSupportedError(VCSError):
52 class VCSBackendNotSupportedError(VCSError):
52 """
53 """
53 Exception raised when VCSServer does not support requested backend
54 Exception raised when VCSServer does not support requested backend
54 """
55 """
55
56
56
57
57 class EmptyRepositoryError(RepositoryError):
58 class EmptyRepositoryError(RepositoryError):
58 pass
59 pass
59
60
60
61
61 class TagAlreadyExistError(RepositoryError):
62 class TagAlreadyExistError(RepositoryError):
62 pass
63 pass
63
64
64
65
65 class TagDoesNotExistError(RepositoryError):
66 class TagDoesNotExistError(RepositoryError):
66 pass
67 pass
67
68
68
69
69 class BranchAlreadyExistError(RepositoryError):
70 class BranchAlreadyExistError(RepositoryError):
70 pass
71 pass
71
72
72
73
73 class BranchDoesNotExistError(RepositoryError):
74 class BranchDoesNotExistError(RepositoryError):
74 pass
75 pass
75
76
76
77
77 class CommitError(RepositoryError):
78 class CommitError(RepositoryError):
78 """
79 """
79 Exceptions related to an existing commit
80 Exceptions related to an existing commit
80 """
81 """
81
82
82
83
83 class CommitDoesNotExistError(CommitError):
84 class CommitDoesNotExistError(CommitError):
84 pass
85 pass
85
86
86
87
87 class CommittingError(RepositoryError):
88 class CommittingError(RepositoryError):
88 """
89 """
89 Exceptions happening while creating a new commit
90 Exceptions happening while creating a new commit
90 """
91 """
91
92
92
93
93 class NothingChangedError(CommittingError):
94 class NothingChangedError(CommittingError):
94 pass
95 pass
95
96
96
97
97 class NodeError(VCSError):
98 class NodeError(VCSError):
98 pass
99 pass
99
100
100
101
101 class RemovedFileNodeError(NodeError):
102 class RemovedFileNodeError(NodeError):
102 pass
103 pass
103
104
104
105
105 class NodeAlreadyExistsError(CommittingError):
106 class NodeAlreadyExistsError(CommittingError):
106 pass
107 pass
107
108
108
109
109 class NodeAlreadyChangedError(CommittingError):
110 class NodeAlreadyChangedError(CommittingError):
110 pass
111 pass
111
112
112
113
113 class NodeDoesNotExistError(CommittingError):
114 class NodeDoesNotExistError(CommittingError):
114 pass
115 pass
115
116
116
117
117 class NodeNotChangedError(CommittingError):
118 class NodeNotChangedError(CommittingError):
118 pass
119 pass
119
120
120
121
121 class NodeAlreadyAddedError(CommittingError):
122 class NodeAlreadyAddedError(CommittingError):
122 pass
123 pass
123
124
124
125
125 class NodeAlreadyRemovedError(CommittingError):
126 class NodeAlreadyRemovedError(CommittingError):
126 pass
127 pass
127
128
128
129
129 class SubrepoMergeError(RepositoryError):
130 class SubrepoMergeError(RepositoryError):
130 """
131 """
131 This happens if we try to merge a repository which contains subrepos and
132 This happens if we try to merge a repository which contains subrepos and
132 the subrepos cannot be merged. The subrepos are not merged itself but
133 the subrepos cannot be merged. The subrepos are not merged itself but
133 their references in the root repo are merged.
134 their references in the root repo are merged.
134 """
135 """
135
136
136
137
137 class ImproperArchiveTypeError(VCSError):
138 class ImproperArchiveTypeError(VCSError):
138 pass
139 pass
139
140
140
141
141 class CommandError(VCSError):
142 class CommandError(VCSError):
142 pass
143 pass
143
144
144
145
145 class UnhandledException(VCSError):
146 class UnhandledException(VCSError):
146 """
147 """
147 Signals that something unexpected went wrong.
148 Signals that something unexpected went wrong.
148
149
149 This usually means we have a programming error on the side of the VCSServer
150 This usually means we have a programming error on the side of the VCSServer
150 and should inspect the logfile of the VCSServer to find more details.
151 and should inspect the logfile of the VCSServer to find more details.
151 """
152 """
152
153
153
154
154 _EXCEPTION_MAP = {
155 _EXCEPTION_MAP = {
155 'abort': RepositoryError,
156 'abort': RepositoryError,
156 'archive': ImproperArchiveTypeError,
157 'archive': ImproperArchiveTypeError,
157 'error': RepositoryError,
158 'error': RepositoryError,
158 'lookup': CommitDoesNotExistError,
159 'lookup': CommitDoesNotExistError,
159 'repo_locked': RepositoryError,
160 'repo_locked': RepositoryError,
160 'requirement': RepositoryRequirementError,
161 'requirement': RepositoryRequirementError,
161 'unhandled': UnhandledException,
162 'unhandled': UnhandledException,
162 # TODO: johbo: Define our own exception for this and stop abusing
163 # TODO: johbo: Define our own exception for this and stop abusing
163 # urllib's exception class.
164 # urllib's exception class.
164 'url_error': urllib2.URLError,
165 'url_error': urllib2.URLError,
165 'subrepo_merge_error': SubrepoMergeError,
166 'subrepo_merge_error': SubrepoMergeError,
166 }
167 }
167
168
168
169
169 def map_vcs_exceptions(func):
170 def map_vcs_exceptions(func):
170 """
171 """
171 Utility to decorate functions so that plain exceptions are translated.
172 Utility to decorate functions so that plain exceptions are translated.
172
173
173 The translation is based on `exc_map` which maps a `str` indicating
174 The translation is based on `exc_map` which maps a `str` indicating
174 the error type into an exception class representing this error inside
175 the error type into an exception class representing this error inside
175 of the vcs layer.
176 of the vcs layer.
176 """
177 """
177
178
178 @functools.wraps(func)
179 @functools.wraps(func)
179 def wrapper(*args, **kwargs):
180 def wrapper(*args, **kwargs):
180 try:
181 try:
181 return func(*args, **kwargs)
182 return func(*args, **kwargs)
182 except Exception as e:
183 except Exception as e:
184 from rhodecode.lib.utils2 import str2bool
185 debug = str2bool(rhodecode.CONFIG.get('debug'))
186
183 # The error middleware adds information if it finds
187 # The error middleware adds information if it finds
184 # __traceback_info__ in a frame object. This way the remote
188 # __traceback_info__ in a frame object. This way the remote
185 # traceback information is made available in error reports.
189 # traceback information is made available in error reports.
186 remote_tb = getattr(e, '_vcs_server_traceback', None)
190 remote_tb = getattr(e, '_vcs_server_traceback', None)
191 org_remote_tb = getattr(e, '_vcs_server_org_exc_tb', '')
187 __traceback_info__ = None
192 __traceback_info__ = None
188 if remote_tb:
193 if remote_tb:
189 if isinstance(remote_tb, basestring):
194 if isinstance(remote_tb, basestring):
190 remote_tb = [remote_tb]
195 remote_tb = [remote_tb]
191 __traceback_info__ = (
196 __traceback_info__ = (
192 'Found VCSServer remote traceback information:\n\n' +
197 'Found VCSServer remote traceback information:\n'
193 '\n'.join(remote_tb))
198 '{}\n'
199 '+++ BEG SOURCE EXCEPTION +++\n\n'
200 '{}\n'
201 '+++ END SOURCE EXCEPTION +++\n'
202 ''.format('\n'.join(remote_tb), org_remote_tb)
203 )
194
204
195 # Avoid that remote_tb also appears in the frame
205 # Avoid that remote_tb also appears in the frame
196 del remote_tb
206 del remote_tb
197
207
198 # Special vcs errors had an attribute "_vcs_kind" which is used
208 # Special vcs errors had an attribute "_vcs_kind" which is used
199 # to translate them to the proper exception class in the vcs
209 # to translate them to the proper exception class in the vcs
200 # client layer.
210 # client layer.
201 kind = getattr(e, '_vcs_kind', None)
211 kind = getattr(e, '_vcs_kind', None)
202
212
203 if kind:
213 if kind:
204 if any(e.args):
214 if any(e.args):
205 args = e.args
215 args = e.args
206 else:
216 else:
207 args = [__traceback_info__ or 'unhandledException']
217 args = [__traceback_info__ or 'unhandledException']
208 if __traceback_info__ and kind not in ['unhandled', 'lookup']:
218 if debug or __traceback_info__ and kind not in ['unhandled', 'lookup']:
209 # for other than unhandled errors also log the traceback
219 # for other than unhandled errors also log the traceback
210 # can be usefull for debugging
220 # can be useful for debugging
211 log.error(__traceback_info__)
221 log.error(__traceback_info__)
212 raise _EXCEPTION_MAP[kind](*args)
222 raise _EXCEPTION_MAP[kind](*args)
213 else:
223 else:
214 raise
224 raise
215 return wrapper
225 return wrapper
General Comments 0
You need to be logged in to leave comments. Login now