##// END OF EJS Templates
vcs: Raise exception if unknown protocol specified for vcs server protocol.
Martin Bornhold -
r960:ebfb39d8 default
parent child Browse files
Show More
@@ -1,290 +1,296 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2014-2016 RhodeCode GmbH
3 # Copyright (C) 2014-2016 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 Various version Control System version lib (vcs) management abstraction layer
22 Various version Control System version lib (vcs) management abstraction layer
23 for Python. Build with server client architecture.
23 for Python. Build with server client architecture.
24 """
24 """
25
25
26
26
27 VERSION = (0, 5, 0, 'dev')
27 VERSION = (0, 5, 0, 'dev')
28
28
29 __version__ = '.'.join((str(each) for each in VERSION[:4]))
29 __version__ = '.'.join((str(each) for each in VERSION[:4]))
30
30
31 __all__ = [
31 __all__ = [
32 'get_version', 'get_vcs_instance', 'get_backend',
32 'get_version', 'get_vcs_instance', 'get_backend',
33 'VCSError', 'RepositoryError', 'CommitError'
33 'VCSError', 'RepositoryError', 'CommitError'
34 ]
34 ]
35
35
36 import atexit
36 import atexit
37 import logging
37 import logging
38 import subprocess
38 import subprocess
39 import time
39 import time
40 import urlparse
40 import urlparse
41 from cStringIO import StringIO
41 from cStringIO import StringIO
42
42
43 import Pyro4
43 import Pyro4
44 from Pyro4.errors import CommunicationError
44 from Pyro4.errors import CommunicationError
45
45
46 from rhodecode.lib.vcs.conf import settings
46 from rhodecode.lib.vcs.conf import settings
47 from rhodecode.lib.vcs.backends import get_vcs_instance, get_backend
47 from rhodecode.lib.vcs.backends import get_vcs_instance, get_backend
48 from rhodecode.lib.vcs.exceptions import (
48 from rhodecode.lib.vcs.exceptions import (
49 VCSError, RepositoryError, CommitError)
49 VCSError, RepositoryError, CommitError)
50
50
51 log = logging.getLogger(__name__)
51 log = logging.getLogger(__name__)
52
52
53 # The pycurl library directly accesses C API functions and is not patched by
53 # The pycurl library directly accesses C API functions and is not patched by
54 # gevent. This will potentially lead to deadlocks due to incompatibility to
54 # gevent. This will potentially lead to deadlocks due to incompatibility to
55 # gevent. Therefore we check if gevent is active and import a gevent compatible
55 # gevent. Therefore we check if gevent is active and import a gevent compatible
56 # wrapper in that case.
56 # wrapper in that case.
57 try:
57 try:
58 from gevent import monkey
58 from gevent import monkey
59 if monkey.is_module_patched('__builtin__'):
59 if monkey.is_module_patched('__builtin__'):
60 import geventcurl as pycurl
60 import geventcurl as pycurl
61 log.debug('Using gevent comapatible pycurl: %s', pycurl)
61 log.debug('Using gevent comapatible pycurl: %s', pycurl)
62 else:
62 else:
63 import pycurl
63 import pycurl
64 except ImportError:
64 except ImportError:
65 import pycurl
65 import pycurl
66
66
67
67
68 def get_version():
68 def get_version():
69 """
69 """
70 Returns shorter version (digit parts only) as string.
70 Returns shorter version (digit parts only) as string.
71 """
71 """
72 return '.'.join((str(each) for each in VERSION[:3]))
72 return '.'.join((str(each) for each in VERSION[:3]))
73
73
74
74
75 def connect_pyro4(server_and_port):
75 def connect_pyro4(server_and_port):
76 from rhodecode.lib.vcs import connection, client
76 from rhodecode.lib.vcs import connection, client
77 from rhodecode.lib.middleware.utils import scm_app
77 from rhodecode.lib.middleware.utils import scm_app
78
78
79 git_remote = client.RequestScopeProxyFactory(
79 git_remote = client.RequestScopeProxyFactory(
80 settings.pyro_remote(settings.PYRO_GIT, server_and_port))
80 settings.pyro_remote(settings.PYRO_GIT, server_and_port))
81 hg_remote = client.RequestScopeProxyFactory(
81 hg_remote = client.RequestScopeProxyFactory(
82 settings.pyro_remote(settings.PYRO_HG, server_and_port))
82 settings.pyro_remote(settings.PYRO_HG, server_and_port))
83 svn_remote = client.RequestScopeProxyFactory(
83 svn_remote = client.RequestScopeProxyFactory(
84 settings.pyro_remote(settings.PYRO_SVN, server_and_port))
84 settings.pyro_remote(settings.PYRO_SVN, server_and_port))
85
85
86 connection.Git = client.RepoMaker(proxy_factory=git_remote)
86 connection.Git = client.RepoMaker(proxy_factory=git_remote)
87 connection.Hg = client.RepoMaker(proxy_factory=hg_remote)
87 connection.Hg = client.RepoMaker(proxy_factory=hg_remote)
88 connection.Svn = client.RepoMaker(proxy_factory=svn_remote)
88 connection.Svn = client.RepoMaker(proxy_factory=svn_remote)
89
89
90 scm_app.GIT_REMOTE_WSGI = Pyro4.Proxy(
90 scm_app.GIT_REMOTE_WSGI = Pyro4.Proxy(
91 settings.pyro_remote(
91 settings.pyro_remote(
92 settings.PYRO_GIT_REMOTE_WSGI, server_and_port))
92 settings.PYRO_GIT_REMOTE_WSGI, server_and_port))
93 scm_app.HG_REMOTE_WSGI = Pyro4.Proxy(
93 scm_app.HG_REMOTE_WSGI = Pyro4.Proxy(
94 settings.pyro_remote(
94 settings.pyro_remote(
95 settings.PYRO_HG_REMOTE_WSGI, server_and_port))
95 settings.PYRO_HG_REMOTE_WSGI, server_and_port))
96
96
97 @atexit.register
97 @atexit.register
98 def free_connection_resources():
98 def free_connection_resources():
99 connection.Git = None
99 connection.Git = None
100 connection.Hg = None
100 connection.Hg = None
101 connection.Svn = None
101 connection.Svn = None
102
102
103
103
104 def connect_http(server_and_port):
104 def connect_http(server_and_port):
105 from rhodecode.lib.vcs import connection, client_http
105 from rhodecode.lib.vcs import connection, client_http
106 from rhodecode.lib.middleware.utils import scm_app
106 from rhodecode.lib.middleware.utils import scm_app
107
107
108 session_factory = client_http.ThreadlocalSessionFactory()
108 session_factory = client_http.ThreadlocalSessionFactory()
109
109
110 connection.Git = client_http.RepoMaker(
110 connection.Git = client_http.RepoMaker(
111 server_and_port, '/git', session_factory)
111 server_and_port, '/git', session_factory)
112 connection.Hg = client_http.RepoMaker(
112 connection.Hg = client_http.RepoMaker(
113 server_and_port, '/hg', session_factory)
113 server_and_port, '/hg', session_factory)
114 connection.Svn = client_http.RepoMaker(
114 connection.Svn = client_http.RepoMaker(
115 server_and_port, '/svn', session_factory)
115 server_and_port, '/svn', session_factory)
116
116
117 scm_app.HG_REMOTE_WSGI = client_http.VcsHttpProxy(
117 scm_app.HG_REMOTE_WSGI = client_http.VcsHttpProxy(
118 server_and_port, '/proxy/hg')
118 server_and_port, '/proxy/hg')
119 scm_app.GIT_REMOTE_WSGI = client_http.VcsHttpProxy(
119 scm_app.GIT_REMOTE_WSGI = client_http.VcsHttpProxy(
120 server_and_port, '/proxy/git')
120 server_and_port, '/proxy/git')
121
121
122 @atexit.register
122 @atexit.register
123 def free_connection_resources():
123 def free_connection_resources():
124 connection.Git = None
124 connection.Git = None
125 connection.Hg = None
125 connection.Hg = None
126 connection.Svn = None
126 connection.Svn = None
127
127
128
128
129 def connect_vcs(server_and_port, protocol):
129 def connect_vcs(server_and_port, protocol):
130 """
130 """
131 Initializes the connection to the vcs server.
131 Initializes the connection to the vcs server.
132
132
133 :param server_and_port: str, e.g. "localhost:9900"
133 :param server_and_port: str, e.g. "localhost:9900"
134 :param protocol: str, "pyro4" or "http"
134 :param protocol: str, "pyro4" or "http"
135 """
135 """
136 if protocol == 'pyro4':
136 if protocol == 'pyro4':
137 connect_pyro4(server_and_port)
137 connect_pyro4(server_and_port)
138 elif protocol == 'http':
138 elif protocol == 'http':
139 connect_http(server_and_port)
139 connect_http(server_and_port)
140 else:
141 raise Exception('Invalid vcs server protocol "{}"'.format(protocol))
140
142
141
143
142 # TODO: johbo: This function should be moved into our test suite, there is
144 # TODO: johbo: This function should be moved into our test suite, there is
143 # no reason to support starting the vcsserver in Enterprise itself.
145 # no reason to support starting the vcsserver in Enterprise itself.
144 def start_vcs_server(server_and_port, protocol, log_level=None):
146 def start_vcs_server(server_and_port, protocol, log_level=None):
145 """
147 """
146 Starts the vcs server in a subprocess.
148 Starts the vcs server in a subprocess.
147 """
149 """
148 log.info('Starting VCSServer as a sub process with %s protocol', protocol)
150 log.info('Starting VCSServer as a sub process with %s protocol', protocol)
149 if protocol == 'http':
151 if protocol == 'http':
150 return _start_http_vcs_server(server_and_port, log_level)
152 return _start_http_vcs_server(server_and_port, log_level)
151 elif protocol == 'pyro4':
153 elif protocol == 'pyro4':
152 return _start_pyro4_vcs_server(server_and_port, log_level)
154 return _start_pyro4_vcs_server(server_and_port, log_level)
155 else:
156 raise Exception('Invalid vcs server protocol "{}"'.format(protocol))
153
157
154
158
155 def _start_pyro4_vcs_server(server_and_port, log_level=None):
159 def _start_pyro4_vcs_server(server_and_port, log_level=None):
156 _try_to_shutdown_running_server(server_and_port, protocol='pyro4')
160 _try_to_shutdown_running_server(server_and_port, protocol='pyro4')
157 host, port = server_and_port.rsplit(":", 1)
161 host, port = server_and_port.rsplit(":", 1)
158 host = host.strip('[]')
162 host = host.strip('[]')
159 args = [
163 args = [
160 'vcsserver', '--port', port, '--host', host, '--locale', 'en_US.UTF-8',
164 'vcsserver', '--port', port, '--host', host, '--locale', 'en_US.UTF-8',
161 '--threadpool', '32']
165 '--threadpool', '32']
162 if log_level:
166 if log_level:
163 args += ['--log-level', log_level]
167 args += ['--log-level', log_level]
164 proc = subprocess.Popen(args)
168 proc = subprocess.Popen(args)
165
169
166 def cleanup_server_process():
170 def cleanup_server_process():
167 proc.kill()
171 proc.kill()
168 atexit.register(cleanup_server_process)
172 atexit.register(cleanup_server_process)
169
173
170 server = create_vcsserver_proxy(server_and_port, protocol='pyro4')
174 server = create_vcsserver_proxy(server_and_port, protocol='pyro4')
171 _wait_until_vcs_server_is_reachable(server)
175 _wait_until_vcs_server_is_reachable(server)
172
176
173
177
174 def _start_http_vcs_server(server_and_port, log_level=None):
178 def _start_http_vcs_server(server_and_port, log_level=None):
175 # TODO: mikhail: shutdown if an http server already runs
179 # TODO: mikhail: shutdown if an http server already runs
176
180
177 host, port = server_and_port.rsplit(":", 1)
181 host, port = server_and_port.rsplit(":", 1)
178 args = [
182 args = [
179 'pserve', 'vcsserver/development_pyramid.ini',
183 'pserve', 'vcsserver/development_pyramid.ini',
180 'http_port=%s' % (port, ), 'http_host=%s' % (host, )]
184 'http_port=%s' % (port, ), 'http_host=%s' % (host, )]
181 proc = subprocess.Popen(args)
185 proc = subprocess.Popen(args)
182
186
183 def cleanup_server_process():
187 def cleanup_server_process():
184 proc.kill()
188 proc.kill()
185 atexit.register(cleanup_server_process)
189 atexit.register(cleanup_server_process)
186
190
187 server = create_vcsserver_proxy(server_and_port, protocol='http')
191 server = create_vcsserver_proxy(server_and_port, protocol='http')
188 _wait_until_vcs_server_is_reachable(server)
192 _wait_until_vcs_server_is_reachable(server)
189
193
190
194
191 def _wait_until_vcs_server_is_reachable(server):
195 def _wait_until_vcs_server_is_reachable(server):
192 while xrange(80): # max 40s of sleep
196 while xrange(80): # max 40s of sleep
193 try:
197 try:
194 server.ping()
198 server.ping()
195 break
199 break
196 except (CommunicationError, pycurl.error):
200 except (CommunicationError, pycurl.error):
197 pass
201 pass
198 time.sleep(0.5)
202 time.sleep(0.5)
199
203
200
204
201 def _try_to_shutdown_running_server(server_and_port, protocol):
205 def _try_to_shutdown_running_server(server_and_port, protocol):
202 server = create_vcsserver_proxy(server_and_port, protocol)
206 server = create_vcsserver_proxy(server_and_port, protocol)
203 try:
207 try:
204 server.shutdown()
208 server.shutdown()
205 except (CommunicationError, pycurl.error):
209 except (CommunicationError, pycurl.error):
206 return
210 return
207
211
208 # TODO: Not sure why this is important, but without it the following start
212 # TODO: Not sure why this is important, but without it the following start
209 # of the server fails.
213 # of the server fails.
210 server = create_vcsserver_proxy(server_and_port, protocol)
214 server = create_vcsserver_proxy(server_and_port, protocol)
211 server.ping()
215 server.ping()
212
216
213
217
214 def create_vcsserver_proxy(server_and_port, protocol):
218 def create_vcsserver_proxy(server_and_port, protocol):
215 if protocol == 'pyro4':
219 if protocol == 'pyro4':
216 return _create_vcsserver_proxy_pyro4(server_and_port)
220 return _create_vcsserver_proxy_pyro4(server_and_port)
217 elif protocol == 'http':
221 elif protocol == 'http':
218 return _create_vcsserver_proxy_http(server_and_port)
222 return _create_vcsserver_proxy_http(server_and_port)
223 else:
224 raise Exception('Invalid vcs server protocol "{}"'.format(protocol))
219
225
220
226
221 def _create_vcsserver_proxy_pyro4(server_and_port):
227 def _create_vcsserver_proxy_pyro4(server_and_port):
222 server = Pyro4.Proxy(
228 server = Pyro4.Proxy(
223 settings.pyro_remote(settings.PYRO_VCSSERVER, server_and_port))
229 settings.pyro_remote(settings.PYRO_VCSSERVER, server_and_port))
224 return server
230 return server
225
231
226
232
227 def _create_vcsserver_proxy_http(server_and_port):
233 def _create_vcsserver_proxy_http(server_and_port):
228 from rhodecode.lib.vcs import client_http
234 from rhodecode.lib.vcs import client_http
229
235
230 session = _create_http_rpc_session()
236 session = _create_http_rpc_session()
231 url = urlparse.urljoin('http://%s' % server_and_port, '/server')
237 url = urlparse.urljoin('http://%s' % server_and_port, '/server')
232 return client_http.RemoteObject(url, session)
238 return client_http.RemoteObject(url, session)
233
239
234
240
235 class CurlSession(object):
241 class CurlSession(object):
236 """
242 """
237 Modeled so that it provides a subset of the requests interface.
243 Modeled so that it provides a subset of the requests interface.
238
244
239 This has been created so that it does only provide a minimal API for our
245 This has been created so that it does only provide a minimal API for our
240 needs. The parts which it provides are based on the API of the library
246 needs. The parts which it provides are based on the API of the library
241 `requests` which allows us to easily benchmark against it.
247 `requests` which allows us to easily benchmark against it.
242
248
243 Please have a look at the class :class:`requests.Session` when you extend
249 Please have a look at the class :class:`requests.Session` when you extend
244 it.
250 it.
245 """
251 """
246
252
247 def __init__(self):
253 def __init__(self):
248 curl = pycurl.Curl()
254 curl = pycurl.Curl()
249 # TODO: johbo: I did test with 7.19 of libcurl. This version has
255 # TODO: johbo: I did test with 7.19 of libcurl. This version has
250 # trouble with 100 - continue being set in the expect header. This
256 # trouble with 100 - continue being set in the expect header. This
251 # can lead to massive performance drops, switching it off here.
257 # can lead to massive performance drops, switching it off here.
252 curl.setopt(curl.HTTPHEADER, ["Expect:"])
258 curl.setopt(curl.HTTPHEADER, ["Expect:"])
253 curl.setopt(curl.TCP_NODELAY, True)
259 curl.setopt(curl.TCP_NODELAY, True)
254 curl.setopt(curl.PROTOCOLS, curl.PROTO_HTTP)
260 curl.setopt(curl.PROTOCOLS, curl.PROTO_HTTP)
255 self._curl = curl
261 self._curl = curl
256
262
257 def post(self, url, data, allow_redirects=False):
263 def post(self, url, data, allow_redirects=False):
258 response_buffer = StringIO()
264 response_buffer = StringIO()
259
265
260 curl = self._curl
266 curl = self._curl
261 curl.setopt(curl.URL, url)
267 curl.setopt(curl.URL, url)
262 curl.setopt(curl.POST, True)
268 curl.setopt(curl.POST, True)
263 curl.setopt(curl.POSTFIELDS, data)
269 curl.setopt(curl.POSTFIELDS, data)
264 curl.setopt(curl.FOLLOWLOCATION, allow_redirects)
270 curl.setopt(curl.FOLLOWLOCATION, allow_redirects)
265 curl.setopt(curl.WRITEDATA, response_buffer)
271 curl.setopt(curl.WRITEDATA, response_buffer)
266 curl.perform()
272 curl.perform()
267
273
268 return CurlResponse(response_buffer)
274 return CurlResponse(response_buffer)
269
275
270
276
271 class CurlResponse(object):
277 class CurlResponse(object):
272 """
278 """
273 The response of a request, modeled after the requests API.
279 The response of a request, modeled after the requests API.
274
280
275 This class provides a subset of the response interface known from the
281 This class provides a subset of the response interface known from the
276 library `requests`. It is intentionally kept similar, so that we can use
282 library `requests`. It is intentionally kept similar, so that we can use
277 `requests` as a drop in replacement for benchmarking purposes.
283 `requests` as a drop in replacement for benchmarking purposes.
278 """
284 """
279
285
280 def __init__(self, response_buffer):
286 def __init__(self, response_buffer):
281 self._response_buffer = response_buffer
287 self._response_buffer = response_buffer
282
288
283 @property
289 @property
284 def content(self):
290 def content(self):
285 return self._response_buffer.getvalue()
291 return self._response_buffer.getvalue()
286
292
287
293
288 def _create_http_rpc_session():
294 def _create_http_rpc_session():
289 session = CurlSession()
295 session = CurlSession()
290 return session
296 return session
General Comments 0
You need to be logged in to leave comments. Login now