##// END OF EJS Templates
vcs: Fix vcsserver startup with http backend.
Martin Bornhold -
r964:eac204f1 default
parent child Browse files
Show More
@@ -1,296 +1,300 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2014-2016 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 """
22 22 Various version Control System version lib (vcs) management abstraction layer
23 23 for Python. Build with server client architecture.
24 24 """
25 25
26 26
27 27 VERSION = (0, 5, 0, 'dev')
28 28
29 29 __version__ = '.'.join((str(each) for each in VERSION[:4]))
30 30
31 31 __all__ = [
32 32 'get_version', 'get_vcs_instance', 'get_backend',
33 33 'VCSError', 'RepositoryError', 'CommitError'
34 34 ]
35 35
36 36 import atexit
37 37 import logging
38 38 import subprocess
39 39 import time
40 40 import urlparse
41 41 from cStringIO import StringIO
42 42
43 43 import Pyro4
44 44 from Pyro4.errors import CommunicationError
45 45
46 46 from rhodecode.lib.vcs.conf import settings
47 47 from rhodecode.lib.vcs.backends import get_vcs_instance, get_backend
48 48 from rhodecode.lib.vcs.exceptions import (
49 VCSError, RepositoryError, CommitError)
49 VCSError, RepositoryError, CommitError, VCSCommunicationError)
50 50
51 51 log = logging.getLogger(__name__)
52 52
53 53 # The pycurl library directly accesses C API functions and is not patched by
54 54 # gevent. This will potentially lead to deadlocks due to incompatibility to
55 55 # gevent. Therefore we check if gevent is active and import a gevent compatible
56 56 # wrapper in that case.
57 57 try:
58 58 from gevent import monkey
59 59 if monkey.is_module_patched('__builtin__'):
60 60 import geventcurl as pycurl
61 61 log.debug('Using gevent comapatible pycurl: %s', pycurl)
62 62 else:
63 63 import pycurl
64 64 except ImportError:
65 65 import pycurl
66 66
67 67
68 68 def get_version():
69 69 """
70 70 Returns shorter version (digit parts only) as string.
71 71 """
72 72 return '.'.join((str(each) for each in VERSION[:3]))
73 73
74 74
75 75 def connect_pyro4(server_and_port):
76 76 from rhodecode.lib.vcs import connection, client
77 77 from rhodecode.lib.middleware.utils import scm_app
78 78
79 79 git_remote = client.RequestScopeProxyFactory(
80 80 settings.pyro_remote(settings.PYRO_GIT, server_and_port))
81 81 hg_remote = client.RequestScopeProxyFactory(
82 82 settings.pyro_remote(settings.PYRO_HG, server_and_port))
83 83 svn_remote = client.RequestScopeProxyFactory(
84 84 settings.pyro_remote(settings.PYRO_SVN, server_and_port))
85 85
86 86 connection.Git = client.RepoMaker(proxy_factory=git_remote)
87 87 connection.Hg = client.RepoMaker(proxy_factory=hg_remote)
88 88 connection.Svn = client.RepoMaker(proxy_factory=svn_remote)
89 89
90 90 scm_app.GIT_REMOTE_WSGI = Pyro4.Proxy(
91 91 settings.pyro_remote(
92 92 settings.PYRO_GIT_REMOTE_WSGI, server_and_port))
93 93 scm_app.HG_REMOTE_WSGI = Pyro4.Proxy(
94 94 settings.pyro_remote(
95 95 settings.PYRO_HG_REMOTE_WSGI, server_and_port))
96 96
97 97 @atexit.register
98 98 def free_connection_resources():
99 99 connection.Git = None
100 100 connection.Hg = None
101 101 connection.Svn = None
102 102
103 103
104 104 def connect_http(server_and_port):
105 105 from rhodecode.lib.vcs import connection, client_http
106 106 from rhodecode.lib.middleware.utils import scm_app
107 107
108 108 session_factory = client_http.ThreadlocalSessionFactory()
109 109
110 110 connection.Git = client_http.RepoMaker(
111 111 server_and_port, '/git', session_factory)
112 112 connection.Hg = client_http.RepoMaker(
113 113 server_and_port, '/hg', session_factory)
114 114 connection.Svn = client_http.RepoMaker(
115 115 server_and_port, '/svn', session_factory)
116 116
117 117 scm_app.HG_REMOTE_WSGI = client_http.VcsHttpProxy(
118 118 server_and_port, '/proxy/hg')
119 119 scm_app.GIT_REMOTE_WSGI = client_http.VcsHttpProxy(
120 120 server_and_port, '/proxy/git')
121 121
122 122 @atexit.register
123 123 def free_connection_resources():
124 124 connection.Git = None
125 125 connection.Hg = None
126 126 connection.Svn = None
127 127
128 128
129 129 def connect_vcs(server_and_port, protocol):
130 130 """
131 131 Initializes the connection to the vcs server.
132 132
133 133 :param server_and_port: str, e.g. "localhost:9900"
134 134 :param protocol: str, "pyro4" or "http"
135 135 """
136 136 if protocol == 'pyro4':
137 137 connect_pyro4(server_and_port)
138 138 elif protocol == 'http':
139 139 connect_http(server_and_port)
140 140 else:
141 141 raise Exception('Invalid vcs server protocol "{}"'.format(protocol))
142 142
143 143
144 144 # TODO: johbo: This function should be moved into our test suite, there is
145 145 # no reason to support starting the vcsserver in Enterprise itself.
146 146 def start_vcs_server(server_and_port, protocol, log_level=None):
147 147 """
148 148 Starts the vcs server in a subprocess.
149 149 """
150 150 log.info('Starting VCSServer as a sub process with %s protocol', protocol)
151 151 if protocol == 'http':
152 152 return _start_http_vcs_server(server_and_port, log_level)
153 153 elif protocol == 'pyro4':
154 154 return _start_pyro4_vcs_server(server_and_port, log_level)
155 155 else:
156 156 raise Exception('Invalid vcs server protocol "{}"'.format(protocol))
157 157
158 158
159 159 def _start_pyro4_vcs_server(server_and_port, log_level=None):
160 160 _try_to_shutdown_running_server(server_and_port, protocol='pyro4')
161 161 host, port = server_and_port.rsplit(":", 1)
162 162 host = host.strip('[]')
163 163 args = [
164 164 'vcsserver', '--port', port, '--host', host, '--locale', 'en_US.UTF-8',
165 165 '--threadpool', '32']
166 166 if log_level:
167 167 args += ['--log-level', log_level]
168 168 proc = subprocess.Popen(args)
169 169
170 170 def cleanup_server_process():
171 171 proc.kill()
172 172 atexit.register(cleanup_server_process)
173 173
174 174 server = create_vcsserver_proxy(server_and_port, protocol='pyro4')
175 175 _wait_until_vcs_server_is_reachable(server)
176 176
177 177
178 178 def _start_http_vcs_server(server_and_port, log_level=None):
179 179 # TODO: mikhail: shutdown if an http server already runs
180 180
181 181 host, port = server_and_port.rsplit(":", 1)
182 182 args = [
183 'pserve', 'vcsserver/development_pyramid.ini',
183 'pserve', 'rhodecode/tests/vcsserver_http.ini',
184 184 'http_port=%s' % (port, ), 'http_host=%s' % (host, )]
185 185 proc = subprocess.Popen(args)
186 186
187 187 def cleanup_server_process():
188 188 proc.kill()
189 189 atexit.register(cleanup_server_process)
190 190
191 191 server = create_vcsserver_proxy(server_and_port, protocol='http')
192 192 _wait_until_vcs_server_is_reachable(server)
193 193
194 194
195 def _wait_until_vcs_server_is_reachable(server):
196 while xrange(80): # max 40s of sleep
195 def _wait_until_vcs_server_is_reachable(server, timeout=40):
196 begin = time.time()
197 while (time.time() - begin) < timeout:
197 198 try:
198 199 server.ping()
199 break
200 except (CommunicationError, pycurl.error):
201 pass
200 return
201 except (VCSCommunicationError, CommunicationError, pycurl.error):
202 log.debug('VCSServer not started yet, retry to connect.')
202 203 time.sleep(0.5)
204 raise Exception(
205 'Starting the VCSServer failed or took more than {} '
206 'seconds.'.format(timeout))
203 207
204 208
205 209 def _try_to_shutdown_running_server(server_and_port, protocol):
206 210 server = create_vcsserver_proxy(server_and_port, protocol)
207 211 try:
208 212 server.shutdown()
209 213 except (CommunicationError, pycurl.error):
210 214 return
211 215
212 216 # TODO: Not sure why this is important, but without it the following start
213 217 # of the server fails.
214 218 server = create_vcsserver_proxy(server_and_port, protocol)
215 219 server.ping()
216 220
217 221
218 222 def create_vcsserver_proxy(server_and_port, protocol):
219 223 if protocol == 'pyro4':
220 224 return _create_vcsserver_proxy_pyro4(server_and_port)
221 225 elif protocol == 'http':
222 226 return _create_vcsserver_proxy_http(server_and_port)
223 227 else:
224 228 raise Exception('Invalid vcs server protocol "{}"'.format(protocol))
225 229
226 230
227 231 def _create_vcsserver_proxy_pyro4(server_and_port):
228 232 server = Pyro4.Proxy(
229 233 settings.pyro_remote(settings.PYRO_VCSSERVER, server_and_port))
230 234 return server
231 235
232 236
233 237 def _create_vcsserver_proxy_http(server_and_port):
234 238 from rhodecode.lib.vcs import client_http
235 239
236 240 session = _create_http_rpc_session()
237 241 url = urlparse.urljoin('http://%s' % server_and_port, '/server')
238 242 return client_http.RemoteObject(url, session)
239 243
240 244
241 245 class CurlSession(object):
242 246 """
243 247 Modeled so that it provides a subset of the requests interface.
244 248
245 249 This has been created so that it does only provide a minimal API for our
246 250 needs. The parts which it provides are based on the API of the library
247 251 `requests` which allows us to easily benchmark against it.
248 252
249 253 Please have a look at the class :class:`requests.Session` when you extend
250 254 it.
251 255 """
252 256
253 257 def __init__(self):
254 258 curl = pycurl.Curl()
255 259 # TODO: johbo: I did test with 7.19 of libcurl. This version has
256 260 # trouble with 100 - continue being set in the expect header. This
257 261 # can lead to massive performance drops, switching it off here.
258 262 curl.setopt(curl.HTTPHEADER, ["Expect:"])
259 263 curl.setopt(curl.TCP_NODELAY, True)
260 264 curl.setopt(curl.PROTOCOLS, curl.PROTO_HTTP)
261 265 self._curl = curl
262 266
263 267 def post(self, url, data, allow_redirects=False):
264 268 response_buffer = StringIO()
265 269
266 270 curl = self._curl
267 271 curl.setopt(curl.URL, url)
268 272 curl.setopt(curl.POST, True)
269 273 curl.setopt(curl.POSTFIELDS, data)
270 274 curl.setopt(curl.FOLLOWLOCATION, allow_redirects)
271 275 curl.setopt(curl.WRITEDATA, response_buffer)
272 276 curl.perform()
273 277
274 278 return CurlResponse(response_buffer)
275 279
276 280
277 281 class CurlResponse(object):
278 282 """
279 283 The response of a request, modeled after the requests API.
280 284
281 285 This class provides a subset of the response interface known from the
282 286 library `requests`. It is intentionally kept similar, so that we can use
283 287 `requests` as a drop in replacement for benchmarking purposes.
284 288 """
285 289
286 290 def __init__(self, response_buffer):
287 291 self._response_buffer = response_buffer
288 292
289 293 @property
290 294 def content(self):
291 295 return self._response_buffer.getvalue()
292 296
293 297
294 298 def _create_http_rpc_session():
295 299 session = CurlSession()
296 300 return session
@@ -1,197 +1,196 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2014-2016 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 """
22 22 Custom vcs exceptions module.
23 23 """
24 24
25 25 import functools
26 26 import urllib2
27 import pycurl
28 from Pyro4.errors import CommunicationError
27
29 28
30 29 class VCSCommunicationError(Exception):
31 30 pass
32 31
33 32
34 33 class PyroVCSCommunicationError(VCSCommunicationError):
35 34 pass
36 35
37 36
38 37 class HttpVCSCommunicationError(VCSCommunicationError):
39 38 pass
40 39
41 40
42 41 class VCSError(Exception):
43 42 pass
44 43
45 44
46 45 class RepositoryError(VCSError):
47 46 pass
48 47
49 48
50 49 class RepositoryRequirementError(RepositoryError):
51 50 pass
52 51
53 52
54 53 class VCSBackendNotSupportedError(VCSError):
55 54 """
56 55 Exception raised when VCSServer does not support requested backend
57 56 """
58 57
59 58
60 59 class EmptyRepositoryError(RepositoryError):
61 60 pass
62 61
63 62
64 63 class TagAlreadyExistError(RepositoryError):
65 64 pass
66 65
67 66
68 67 class TagDoesNotExistError(RepositoryError):
69 68 pass
70 69
71 70
72 71 class BranchAlreadyExistError(RepositoryError):
73 72 pass
74 73
75 74
76 75 class BranchDoesNotExistError(RepositoryError):
77 76 pass
78 77
79 78
80 79 class CommitError(RepositoryError):
81 80 """
82 81 Exceptions related to an existing commit
83 82 """
84 83
85 84
86 85 class CommitDoesNotExistError(CommitError):
87 86 pass
88 87
89 88
90 89 class CommittingError(RepositoryError):
91 90 """
92 91 Exceptions happening while creating a new commit
93 92 """
94 93
95 94
96 95 class NothingChangedError(CommittingError):
97 96 pass
98 97
99 98
100 99 class NodeError(VCSError):
101 100 pass
102 101
103 102
104 103 class RemovedFileNodeError(NodeError):
105 104 pass
106 105
107 106
108 107 class NodeAlreadyExistsError(CommittingError):
109 108 pass
110 109
111 110
112 111 class NodeAlreadyChangedError(CommittingError):
113 112 pass
114 113
115 114
116 115 class NodeDoesNotExistError(CommittingError):
117 116 pass
118 117
119 118
120 119 class NodeNotChangedError(CommittingError):
121 120 pass
122 121
123 122
124 123 class NodeAlreadyAddedError(CommittingError):
125 124 pass
126 125
127 126
128 127 class NodeAlreadyRemovedError(CommittingError):
129 128 pass
130 129
131 130
132 131 class ImproperArchiveTypeError(VCSError):
133 132 pass
134 133
135 134
136 135 class CommandError(VCSError):
137 136 pass
138 137
139 138
140 139 class UnhandledException(VCSError):
141 140 """
142 141 Signals that something unexpected went wrong.
143 142
144 143 This usually means we have a programming error on the side of the VCSServer
145 144 and should inspect the logfile of the VCSServer to find more details.
146 145 """
147 146
148 147
149 148 _EXCEPTION_MAP = {
150 149 'abort': RepositoryError,
151 150 'archive': ImproperArchiveTypeError,
152 151 'error': RepositoryError,
153 152 'lookup': CommitDoesNotExistError,
154 153 'repo_locked': RepositoryError,
155 154 'requirement': RepositoryRequirementError,
156 155 'unhandled': UnhandledException,
157 156 # TODO: johbo: Define our own exception for this and stop abusing
158 157 # urllib's exception class.
159 158 'url_error': urllib2.URLError,
160 159 }
161 160
162 161
163 162 def map_vcs_exceptions(func):
164 163 """
165 164 Utility to decorate functions so that plain exceptions are translated.
166 165
167 166 The translation is based on `exc_map` which maps a `str` indicating
168 167 the error type into an exception class representing this error inside
169 168 of the vcs layer.
170 169 """
171 170
172 171 @functools.wraps(func)
173 172 def wrapper(*args, **kwargs):
174 173 try:
175 174 return func(*args, **kwargs)
176 175 except Exception as e:
177 176 # The error middleware adds information if it finds
178 177 # __traceback_info__ in a frame object. This way the remote
179 178 # traceback information is made available in error reports.
180 179 remote_tb = getattr(e, '_pyroTraceback', None)
181 180 if remote_tb:
182 181 __traceback_info__ = (
183 182 'Found Pyro4 remote traceback information:\n\n' +
184 183 '\n'.join(remote_tb))
185 184
186 185 # Avoid that remote_tb also appears in the frame
187 186 del remote_tb
188 187
189 188 # Special vcs errors had an attribute "_vcs_kind" which is used
190 189 # to translate them to the proper exception class in the vcs
191 190 # client layer.
192 191 kind = getattr(e, '_vcs_kind', None)
193 192 if kind:
194 193 raise _EXCEPTION_MAP[kind](*e.args)
195 194 else:
196 195 raise
197 196 return wrapper
General Comments 0
You need to be logged in to leave comments. Login now