/brz/remove-bazaar

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar

« back to all changes in this revision

Viewing changes to bzrlib/smart/server.py

merge bzr.dev.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2006, 2007 Canonical Ltd
 
1
# Copyright (C) 2006, 2007, 2008 Canonical Ltd
2
2
#
3
3
# This program is free software; you can redistribute it and/or modify
4
4
# it under the terms of the GNU General Public License as published by
12
12
#
13
13
# You should have received a copy of the GNU General Public License
14
14
# along with this program; if not, write to the Free Software
15
 
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
16
16
 
17
17
"""Server for smart-server protocol."""
18
18
 
19
19
import errno
20
20
import socket
 
21
import sys
21
22
import threading
22
23
 
23
 
from bzrlib.hooks import Hooks
 
24
from bzrlib.hooks import HookPoint, Hooks
24
25
from bzrlib import (
25
26
    errors,
26
27
    trace,
27
28
    transport,
28
29
)
29
 
from bzrlib.smart.medium import SmartServerSocketStreamMedium
 
30
from bzrlib.lazy_import import lazy_import
 
31
lazy_import(globals(), """
 
32
from bzrlib.smart import medium
 
33
""")
30
34
 
31
35
 
32
36
class SmartTCPServer(object):
38
42
    hooks: An instance of SmartServerHooks.
39
43
    """
40
44
 
41
 
    def __init__(self, backing_transport, host='127.0.0.1', port=0):
 
45
    def __init__(self, backing_transport, host='127.0.0.1', port=0,
 
46
                 root_client_path='/'):
42
47
        """Construct a new server.
43
48
 
44
49
        To actually start it running, call either start_background_thread or
45
50
        serve.
46
51
 
 
52
        :param backing_transport: The transport to serve.
47
53
        :param host: Name of the interface to listen on.
48
54
        :param port: TCP port to listen on, or 0 to allocate a transient port.
 
55
        :param root_client_path: The client path that will correspond to root
 
56
            of backing_transport.
49
57
        """
50
58
        # let connections timeout so that we get a chance to terminate
51
59
        # Keep a reference to the exceptions we want to catch because the socket
54
62
        from socket import error as socket_error
55
63
        self._socket_error = socket_error
56
64
        self._socket_timeout = socket_timeout
57
 
        self._server_socket = socket.socket()
58
 
        self._server_socket.bind((host, port))
 
65
        addrs = socket.getaddrinfo(host, port, socket.AF_UNSPEC,
 
66
            socket.SOCK_STREAM, 0, socket.AI_PASSIVE)[0]
 
67
 
 
68
        (family, socktype, proto, canonname, sockaddr) = addrs
 
69
 
 
70
        self._server_socket = socket.socket(family, socktype, proto)
 
71
        # SO_REUSERADDR has a different meaning on Windows
 
72
        if sys.platform != 'win32':
 
73
            self._server_socket.setsockopt(socket.SOL_SOCKET,
 
74
                socket.SO_REUSEADDR, 1)
 
75
        try:
 
76
            self._server_socket.bind(sockaddr)
 
77
        except self._socket_error, message:
 
78
            raise errors.CannotBindAddress(host, port, message)
59
79
        self._sockname = self._server_socket.getsockname()
60
80
        self.port = self._sockname[1]
61
81
        self._server_socket.listen(1)
63
83
        self.backing_transport = backing_transport
64
84
        self._started = threading.Event()
65
85
        self._stopped = threading.Event()
 
86
        self.root_client_path = root_client_path
66
87
 
67
 
    def serve(self):
 
88
    def serve(self, thread_name_suffix=''):
68
89
        self._should_terminate = False
69
90
        # for hooks we are letting code know that a server has started (and
70
91
        # later stopped).
77
98
        # We need all three because:
78
99
        #  * other machines see the first
79
100
        #  * local commits on this machine should be able to be mapped to
80
 
        #    this server 
 
101
        #    this server
81
102
        #  * commits the server does itself need to be mapped across to this
82
103
        #    server.
83
104
        # The latter two urls are different aliases to the servers url,
84
 
        # so we group those in a list - as there might be more aliases 
 
105
        # so we group those in a list - as there might be more aliases
85
106
        # in the future.
86
107
        backing_urls = [self.backing_transport.base]
87
108
        try:
106
127
                        if e.args[0] != errno.EBADF:
107
128
                            trace.warning("listening socket error: %s", e)
108
129
                    else:
109
 
                        self.serve_conn(conn)
 
130
                        self.serve_conn(conn, thread_name_suffix)
110
131
            except KeyboardInterrupt:
111
132
                # dont log when CTRL-C'd.
112
133
                raise
129
150
        """Return the url of the server"""
130
151
        return "bzr://%s:%d/" % self._sockname
131
152
 
132
 
    def serve_conn(self, conn):
 
153
    def serve_conn(self, conn, thread_name_suffix):
133
154
        # For WIN32, where the timeout value from the listening socket
134
 
        # propogates to the newly accepted socket.
 
155
        # propagates to the newly accepted socket.
135
156
        conn.setblocking(True)
136
157
        conn.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
137
 
        handler = SmartServerSocketStreamMedium(conn, self.backing_transport)
138
 
        connection_thread = threading.Thread(None, handler.serve, name='smart-server-child')
 
158
        handler = medium.SmartServerSocketStreamMedium(
 
159
            conn, self.backing_transport, self.root_client_path)
 
160
        thread_name = 'smart-server-child' + thread_name_suffix
 
161
        connection_thread = threading.Thread(
 
162
            None, handler.serve, name=thread_name)
139
163
        connection_thread.setDaemon(True)
140
164
        connection_thread.start()
141
165
 
142
 
    def start_background_thread(self):
 
166
    def start_background_thread(self, thread_name_suffix=''):
143
167
        self._started.clear()
144
168
        self._server_thread = threading.Thread(None,
145
 
                self.serve,
 
169
                self.serve, args=(thread_name_suffix,),
146
170
                name='server-' + self.get_url())
147
171
        self._server_thread.setDaemon(True)
148
172
        self._server_thread.start()
154
178
        self._should_terminate = True
155
179
        # close the socket - gives error to connections from here on in,
156
180
        # rather than a connection reset error to connections made during
157
 
        # the period between setting _should_terminate = True and 
 
181
        # the period between setting _should_terminate = True and
158
182
        # the current request completing/aborting. It may also break out the
159
183
        # main loop if it was currently in accept() (on some platforms).
160
184
        try:
184
208
        notified.
185
209
        """
186
210
        Hooks.__init__(self)
187
 
        # Introduced in 0.16:
188
 
        # invoked whenever the server starts serving a directory.
189
 
        # The api signature is (backing urls, public url).
190
 
        self['server_started'] = []
191
 
        # Introduced in 0.16:
192
 
        # invoked whenever the server stops serving a directory.
193
 
        # The api signature is (backing urls, public url).
194
 
        self['server_stopped'] = []
 
211
        self.create_hook(HookPoint('server_started',
 
212
            "Called by the bzr server when it starts serving a directory. "
 
213
            "server_started is called with (backing urls, public url), "
 
214
            "where backing_url is a list of URLs giving the "
 
215
            "server-specific directory locations, and public_url is the "
 
216
            "public URL for the directory being served.", (0, 16), None))
 
217
        self.create_hook(HookPoint('server_stopped',
 
218
            "Called by the bzr server when it stops serving a directory. "
 
219
            "server_stopped is called with the same parameters as the "
 
220
            "server_started hook: (backing_urls, public_url).", (0, 16), None))
195
221
 
196
222
SmartTCPServer.hooks = SmartServerHooks()
197
223
 
198
224
 
199
225
class SmartTCPServer_for_testing(SmartTCPServer):
200
226
    """Server suitable for use by transport tests.
201
 
    
 
227
 
202
228
    This server is backed by the process's cwd.
203
229
    """
204
230
 
205
 
    def __init__(self):
 
231
    def __init__(self, thread_name_suffix=''):
206
232
        SmartTCPServer.__init__(self, None)
207
 
        
 
233
        self.client_path_extra = None
 
234
        self.thread_name_suffix = thread_name_suffix
 
235
 
208
236
    def get_backing_transport(self, backing_transport_server):
209
237
        """Get a backing transport from a server we are decorating."""
210
238
        return transport.get_transport(backing_transport_server.get_url())
211
239
 
212
 
    def setUp(self, backing_transport_server=None):
213
 
        """Set up server for testing"""
 
240
    def setUp(self, backing_transport_server=None,
 
241
              client_path_extra='/extra/'):
 
242
        """Set up server for testing.
 
243
 
 
244
        :param backing_transport_server: backing server to use.  If not
 
245
            specified, a LocalURLServer at the current working directory will
 
246
            be used.
 
247
        :param client_path_extra: a path segment starting with '/' to append to
 
248
            the root URL for this server.  For instance, a value of '/foo/bar/'
 
249
            will mean the root of the backing transport will be published at a
 
250
            URL like `bzr://127.0.0.1:nnnn/foo/bar/`, rather than
 
251
            `bzr://127.0.0.1:nnnn/`.  Default value is `extra`, so that tests
 
252
            by default will fail unless they do the necessary path translation.
 
253
        """
 
254
        if not client_path_extra.startswith('/'):
 
255
            raise ValueError(client_path_extra)
214
256
        from bzrlib.transport.chroot import ChrootServer
215
257
        if backing_transport_server is None:
216
258
            from bzrlib.transport.local import LocalURLServer
220
262
        self.chroot_server.setUp()
221
263
        self.backing_transport = transport.get_transport(
222
264
            self.chroot_server.get_url())
223
 
        self.start_background_thread()
 
265
        self.root_client_path = self.client_path_extra = client_path_extra
 
266
        self.start_background_thread(self.thread_name_suffix)
224
267
 
225
268
    def tearDown(self):
226
269
        self.stop_background_thread()
227
270
        self.chroot_server.tearDown()
228
271
 
 
272
    def get_url(self):
 
273
        url = super(SmartTCPServer_for_testing, self).get_url()
 
274
        return url[:-1] + self.client_path_extra
 
275
 
229
276
    def get_bogus_url(self):
230
277
        """Return a URL which will fail to connect"""
231
278
        return 'bzr://127.0.0.1:1/'
239
286
        url = 'readonly+' + backing_transport_server.get_url()
240
287
        return transport.get_transport(url)
241
288
 
 
289
 
 
290
class SmartTCPServer_for_testing_v2_only(SmartTCPServer_for_testing):
 
291
    """A variation of SmartTCPServer_for_testing that limits the client to
 
292
    using RPCs in protocol v2 (i.e. bzr <= 1.5).
 
293
    """
 
294
 
 
295
    def get_url(self):
 
296
        url = super(SmartTCPServer_for_testing_v2_only, self).get_url()
 
297
        url = 'bzr-v2://' + url[len('bzr://'):]
 
298
        return url
 
299
 
 
300
 
 
301
class ReadonlySmartTCPServer_for_testing_v2_only(SmartTCPServer_for_testing_v2_only):
 
302
    """Get a readonly server for testing."""
 
303
 
 
304
    def get_backing_transport(self, backing_transport_server):
 
305
        """Get a backing transport from a server we are decorating."""
 
306
        url = 'readonly+' + backing_transport_server.get_url()
 
307
        return transport.get_transport(url)
 
308
 
 
309