##// 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 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 49 VCSError, RepositoryError, CommitError)
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 else:
141 raise Exception('Invalid vcs server protocol "{}"'.format(protocol))
140 142
141 143
142 144 # TODO: johbo: This function should be moved into our test suite, there is
143 145 # no reason to support starting the vcsserver in Enterprise itself.
144 146 def start_vcs_server(server_and_port, protocol, log_level=None):
145 147 """
146 148 Starts the vcs server in a subprocess.
147 149 """
148 150 log.info('Starting VCSServer as a sub process with %s protocol', protocol)
149 151 if protocol == 'http':
150 152 return _start_http_vcs_server(server_and_port, log_level)
151 153 elif protocol == 'pyro4':
152 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 159 def _start_pyro4_vcs_server(server_and_port, log_level=None):
156 160 _try_to_shutdown_running_server(server_and_port, protocol='pyro4')
157 161 host, port = server_and_port.rsplit(":", 1)
158 162 host = host.strip('[]')
159 163 args = [
160 164 'vcsserver', '--port', port, '--host', host, '--locale', 'en_US.UTF-8',
161 165 '--threadpool', '32']
162 166 if log_level:
163 167 args += ['--log-level', log_level]
164 168 proc = subprocess.Popen(args)
165 169
166 170 def cleanup_server_process():
167 171 proc.kill()
168 172 atexit.register(cleanup_server_process)
169 173
170 174 server = create_vcsserver_proxy(server_and_port, protocol='pyro4')
171 175 _wait_until_vcs_server_is_reachable(server)
172 176
173 177
174 178 def _start_http_vcs_server(server_and_port, log_level=None):
175 179 # TODO: mikhail: shutdown if an http server already runs
176 180
177 181 host, port = server_and_port.rsplit(":", 1)
178 182 args = [
179 183 'pserve', 'vcsserver/development_pyramid.ini',
180 184 'http_port=%s' % (port, ), 'http_host=%s' % (host, )]
181 185 proc = subprocess.Popen(args)
182 186
183 187 def cleanup_server_process():
184 188 proc.kill()
185 189 atexit.register(cleanup_server_process)
186 190
187 191 server = create_vcsserver_proxy(server_and_port, protocol='http')
188 192 _wait_until_vcs_server_is_reachable(server)
189 193
190 194
191 195 def _wait_until_vcs_server_is_reachable(server):
192 196 while xrange(80): # max 40s of sleep
193 197 try:
194 198 server.ping()
195 199 break
196 200 except (CommunicationError, pycurl.error):
197 201 pass
198 202 time.sleep(0.5)
199 203
200 204
201 205 def _try_to_shutdown_running_server(server_and_port, protocol):
202 206 server = create_vcsserver_proxy(server_and_port, protocol)
203 207 try:
204 208 server.shutdown()
205 209 except (CommunicationError, pycurl.error):
206 210 return
207 211
208 212 # TODO: Not sure why this is important, but without it the following start
209 213 # of the server fails.
210 214 server = create_vcsserver_proxy(server_and_port, protocol)
211 215 server.ping()
212 216
213 217
214 218 def create_vcsserver_proxy(server_and_port, protocol):
215 219 if protocol == 'pyro4':
216 220 return _create_vcsserver_proxy_pyro4(server_and_port)
217 221 elif protocol == 'http':
218 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 227 def _create_vcsserver_proxy_pyro4(server_and_port):
222 228 server = Pyro4.Proxy(
223 229 settings.pyro_remote(settings.PYRO_VCSSERVER, server_and_port))
224 230 return server
225 231
226 232
227 233 def _create_vcsserver_proxy_http(server_and_port):
228 234 from rhodecode.lib.vcs import client_http
229 235
230 236 session = _create_http_rpc_session()
231 237 url = urlparse.urljoin('http://%s' % server_and_port, '/server')
232 238 return client_http.RemoteObject(url, session)
233 239
234 240
235 241 class CurlSession(object):
236 242 """
237 243 Modeled so that it provides a subset of the requests interface.
238 244
239 245 This has been created so that it does only provide a minimal API for our
240 246 needs. The parts which it provides are based on the API of the library
241 247 `requests` which allows us to easily benchmark against it.
242 248
243 249 Please have a look at the class :class:`requests.Session` when you extend
244 250 it.
245 251 """
246 252
247 253 def __init__(self):
248 254 curl = pycurl.Curl()
249 255 # TODO: johbo: I did test with 7.19 of libcurl. This version has
250 256 # trouble with 100 - continue being set in the expect header. This
251 257 # can lead to massive performance drops, switching it off here.
252 258 curl.setopt(curl.HTTPHEADER, ["Expect:"])
253 259 curl.setopt(curl.TCP_NODELAY, True)
254 260 curl.setopt(curl.PROTOCOLS, curl.PROTO_HTTP)
255 261 self._curl = curl
256 262
257 263 def post(self, url, data, allow_redirects=False):
258 264 response_buffer = StringIO()
259 265
260 266 curl = self._curl
261 267 curl.setopt(curl.URL, url)
262 268 curl.setopt(curl.POST, True)
263 269 curl.setopt(curl.POSTFIELDS, data)
264 270 curl.setopt(curl.FOLLOWLOCATION, allow_redirects)
265 271 curl.setopt(curl.WRITEDATA, response_buffer)
266 272 curl.perform()
267 273
268 274 return CurlResponse(response_buffer)
269 275
270 276
271 277 class CurlResponse(object):
272 278 """
273 279 The response of a request, modeled after the requests API.
274 280
275 281 This class provides a subset of the response interface known from the
276 282 library `requests`. It is intentionally kept similar, so that we can use
277 283 `requests` as a drop in replacement for benchmarking purposes.
278 284 """
279 285
280 286 def __init__(self, response_buffer):
281 287 self._response_buffer = response_buffer
282 288
283 289 @property
284 290 def content(self):
285 291 return self._response_buffer.getvalue()
286 292
287 293
288 294 def _create_http_rpc_session():
289 295 session = CurlSession()
290 296 return session
General Comments 0
You need to be logged in to leave comments. Login now