~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/smart/server.py

  • Committer: Canonical.com Patch Queue Manager
  • Date: 2011-08-17 18:13:57 UTC
  • mfrom: (5268.7.29 transport-segments)
  • Revision ID: pqm@pqm.ubuntu.com-20110817181357-y5q5eth1hk8bl3om
(jelmer) Allow specifying the colocated branch to use in the branch URL,
 and retrieving the branch name using ControlDir._get_selected_branch.
 (Jelmer Vernooij)

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2006, 2007, 2008 Canonical Ltd
 
1
# Copyright (C) 2006-2011 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
22
22
import sys
23
23
import threading
24
24
 
25
 
from bzrlib.hooks import HookPoint, Hooks
 
25
from bzrlib.hooks import Hooks
26
26
from bzrlib import (
27
27
    errors,
28
28
    trace,
29
 
    transport,
 
29
    transport as _mod_transport,
30
30
)
31
31
from bzrlib.lazy_import import lazy_import
32
32
lazy_import(globals(), """
33
33
from bzrlib.smart import medium
34
34
from bzrlib.transport import (
35
35
    chroot,
36
 
    get_transport,
37
36
    pathfilter,
38
37
    )
39
38
from bzrlib import (
51
50
    hooks: An instance of SmartServerHooks.
52
51
    """
53
52
 
54
 
    def __init__(self, backing_transport, host='127.0.0.1', port=0,
55
 
                 root_client_path='/'):
 
53
    def __init__(self, backing_transport, root_client_path='/'):
56
54
        """Construct a new server.
57
55
 
58
56
        To actually start it running, call either start_background_thread or
59
57
        serve.
60
58
 
61
59
        :param backing_transport: The transport to serve.
 
60
        :param root_client_path: The client path that will correspond to root
 
61
            of backing_transport.
 
62
        """
 
63
        self.backing_transport = backing_transport
 
64
        self.root_client_path = root_client_path
 
65
 
 
66
    def start_server(self, host, port):
 
67
        """Create the server listening socket.
 
68
 
62
69
        :param host: Name of the interface to listen on.
63
70
        :param port: TCP port to listen on, or 0 to allocate a transient port.
64
 
        :param root_client_path: The client path that will correspond to root
65
 
            of backing_transport.
66
71
        """
67
72
        # let connections timeout so that we get a chance to terminate
68
73
        # Keep a reference to the exceptions we want to catch because the socket
89
94
        self.port = self._sockname[1]
90
95
        self._server_socket.listen(1)
91
96
        self._server_socket.settimeout(1)
92
 
        self.backing_transport = backing_transport
93
97
        self._started = threading.Event()
94
98
        self._stopped = threading.Event()
95
 
        self.root_client_path = root_client_path
96
99
 
97
 
    def serve(self, thread_name_suffix=''):
98
 
        self._should_terminate = False
99
 
        # for hooks we are letting code know that a server has started (and
100
 
        # later stopped).
 
100
    def _backing_urls(self):
101
101
        # There are three interesting urls:
102
102
        # The URL the server can be contacted on. (e.g. bzr://host/)
103
103
        # The URL that a commit done on the same machine as the server will
104
104
        # have within the servers space. (e.g. file:///home/user/source)
105
105
        # The URL that will be given to other hooks in the same process -
106
 
        # the URL of the backing transport itself. (e.g. chroot+:///)
 
106
        # the URL of the backing transport itself. (e.g. filtered-36195:///)
107
107
        # We need all three because:
108
108
        #  * other machines see the first
109
109
        #  * local commits on this machine should be able to be mapped to
113
113
        # The latter two urls are different aliases to the servers url,
114
114
        # so we group those in a list - as there might be more aliases
115
115
        # in the future.
116
 
        backing_urls = [self.backing_transport.base]
 
116
        urls = [self.backing_transport.base]
117
117
        try:
118
 
            backing_urls.append(self.backing_transport.external_url())
 
118
            urls.append(self.backing_transport.external_url())
119
119
        except errors.InProcessTransport:
120
120
            pass
 
121
        return urls
 
122
 
 
123
    def run_server_started_hooks(self, backing_urls=None):
 
124
        if backing_urls is None:
 
125
            backing_urls = self._backing_urls()
121
126
        for hook in SmartTCPServer.hooks['server_started']:
122
127
            hook(backing_urls, self.get_url())
123
128
        for hook in SmartTCPServer.hooks['server_started_ex']:
124
129
            hook(backing_urls, self)
 
130
 
 
131
    def run_server_stopped_hooks(self, backing_urls=None):
 
132
        if backing_urls is None:
 
133
            backing_urls = self._backing_urls()
 
134
        for hook in SmartTCPServer.hooks['server_stopped']:
 
135
            hook(backing_urls, self.get_url())
 
136
 
 
137
    def serve(self, thread_name_suffix=''):
 
138
        self._should_terminate = False
 
139
        # for hooks we are letting code know that a server has started (and
 
140
        # later stopped).
 
141
        self.run_server_started_hooks()
125
142
        self._started.set()
126
143
        try:
127
144
            try:
138
155
                        if e.args[0] != errno.EBADF:
139
156
                            trace.warning("listening socket error: %s", e)
140
157
                    else:
 
158
                        if self._should_terminate:
 
159
                            break
141
160
                        self.serve_conn(conn, thread_name_suffix)
142
161
            except KeyboardInterrupt:
143
162
                # dont log when CTRL-C'd.
153
172
            except self._socket_error:
154
173
                # ignore errors on close
155
174
                pass
156
 
            for hook in SmartTCPServer.hooks['server_stopped']:
157
 
                hook(backing_urls, self.get_url())
 
175
            self.run_server_stopped_hooks()
158
176
 
159
177
    def get_url(self):
160
178
        """Return the url of the server"""
161
 
        return "bzr://%s:%d/" % self._sockname
 
179
        return "bzr://%s:%s/" % (self._sockname[0], self._sockname[1])
162
180
 
163
181
    def serve_conn(self, conn, thread_name_suffix):
164
182
        # For WIN32, where the timeout value from the listening socket
170
188
        thread_name = 'smart-server-child' + thread_name_suffix
171
189
        connection_thread = threading.Thread(
172
190
            None, handler.serve, name=thread_name)
 
191
        # FIXME: This thread is never joined, it should at least be collected
 
192
        # somewhere so that tests that want to check for leaked threads can get
 
193
        # rid of them -- vila 20100531
173
194
        connection_thread.setDaemon(True)
174
195
        connection_thread.start()
 
196
        return connection_thread
175
197
 
176
198
    def start_background_thread(self, thread_name_suffix=''):
177
199
        self._started.clear()
217
239
        These are all empty initially, because by default nothing should get
218
240
        notified.
219
241
        """
220
 
        Hooks.__init__(self)
221
 
        self.create_hook(HookPoint('server_started',
 
242
        Hooks.__init__(self, "bzrlib.smart.server", "SmartTCPServer.hooks")
 
243
        self.add_hook('server_started',
222
244
            "Called by the bzr server when it starts serving a directory. "
223
245
            "server_started is called with (backing urls, public url), "
224
246
            "where backing_url is a list of URLs giving the "
225
247
            "server-specific directory locations, and public_url is the "
226
 
            "public URL for the directory being served.", (0, 16), None))
227
 
        self.create_hook(HookPoint('server_started_ex',
 
248
            "public URL for the directory being served.", (0, 16))
 
249
        self.add_hook('server_started_ex',
228
250
            "Called by the bzr server when it starts serving a directory. "
229
251
            "server_started is called with (backing_urls, server_obj).",
230
 
            (1, 17), None))
231
 
        self.create_hook(HookPoint('server_stopped',
 
252
            (1, 17))
 
253
        self.add_hook('server_stopped',
232
254
            "Called by the bzr server when it stops serving a directory. "
233
255
            "server_stopped is called with the same parameters as the "
234
 
            "server_started hook: (backing_urls, public_url).", (0, 16), None))
 
256
            "server_started hook: (backing_urls, public_url).", (0, 16))
 
257
        self.add_hook('server_exception',
 
258
            "Called by the bzr server when an exception occurs. "
 
259
            "server_exception is called with the sys.exc_info() tuple "
 
260
            "return true for the hook if the exception has been handled, "
 
261
            "in which case the server will exit normally.", (2, 4))
235
262
 
236
263
SmartTCPServer.hooks = SmartServerHooks()
237
264
 
238
265
 
239
 
class SmartTCPServer_for_testing(SmartTCPServer):
240
 
    """Server suitable for use by transport tests.
241
 
 
242
 
    This server is backed by the process's cwd.
243
 
    """
244
 
 
245
 
    def __init__(self, thread_name_suffix=''):
246
 
        SmartTCPServer.__init__(self, None)
247
 
        self.client_path_extra = None
248
 
        self.thread_name_suffix = thread_name_suffix
249
 
 
250
 
    def get_backing_transport(self, backing_transport_server):
251
 
        """Get a backing transport from a server we are decorating."""
252
 
        return transport.get_transport(backing_transport_server.get_url())
253
 
 
254
 
    def start_server(self, backing_transport_server=None,
255
 
              client_path_extra='/extra/'):
256
 
        """Set up server for testing.
257
 
 
258
 
        :param backing_transport_server: backing server to use.  If not
259
 
            specified, a LocalURLServer at the current working directory will
260
 
            be used.
261
 
        :param client_path_extra: a path segment starting with '/' to append to
262
 
            the root URL for this server.  For instance, a value of '/foo/bar/'
263
 
            will mean the root of the backing transport will be published at a
264
 
            URL like `bzr://127.0.0.1:nnnn/foo/bar/`, rather than
265
 
            `bzr://127.0.0.1:nnnn/`.  Default value is `extra`, so that tests
266
 
            by default will fail unless they do the necessary path translation.
267
 
        """
268
 
        if not client_path_extra.startswith('/'):
269
 
            raise ValueError(client_path_extra)
270
 
        from bzrlib.transport.chroot import ChrootServer
271
 
        if backing_transport_server is None:
272
 
            from bzrlib.transport.local import LocalURLServer
273
 
            backing_transport_server = LocalURLServer()
274
 
        self.chroot_server = ChrootServer(
275
 
            self.get_backing_transport(backing_transport_server))
276
 
        self.chroot_server.start_server()
277
 
        self.backing_transport = transport.get_transport(
278
 
            self.chroot_server.get_url())
279
 
        self.root_client_path = self.client_path_extra = client_path_extra
280
 
        self.start_background_thread(self.thread_name_suffix)
281
 
 
282
 
    def stop_server(self):
283
 
        self.stop_background_thread()
284
 
        self.chroot_server.stop_server()
285
 
 
286
 
    def get_url(self):
287
 
        url = super(SmartTCPServer_for_testing, self).get_url()
288
 
        return url[:-1] + self.client_path_extra
289
 
 
290
 
    def get_bogus_url(self):
291
 
        """Return a URL which will fail to connect"""
292
 
        return 'bzr://127.0.0.1:1/'
293
 
 
294
 
 
295
 
class ReadonlySmartTCPServer_for_testing(SmartTCPServer_for_testing):
296
 
    """Get a readonly server for testing."""
297
 
 
298
 
    def get_backing_transport(self, backing_transport_server):
299
 
        """Get a backing transport from a server we are decorating."""
300
 
        url = 'readonly+' + backing_transport_server.get_url()
301
 
        return transport.get_transport(url)
302
 
 
303
 
 
304
 
class SmartTCPServer_for_testing_v2_only(SmartTCPServer_for_testing):
305
 
    """A variation of SmartTCPServer_for_testing that limits the client to
306
 
    using RPCs in protocol v2 (i.e. bzr <= 1.5).
307
 
    """
308
 
 
309
 
    def get_url(self):
310
 
        url = super(SmartTCPServer_for_testing_v2_only, self).get_url()
311
 
        url = 'bzr-v2://' + url[len('bzr://'):]
312
 
        return url
313
 
 
314
 
 
315
 
class ReadonlySmartTCPServer_for_testing_v2_only(SmartTCPServer_for_testing_v2_only):
316
 
    """Get a readonly server for testing."""
317
 
 
318
 
    def get_backing_transport(self, backing_transport_server):
319
 
        """Get a backing transport from a server we are decorating."""
320
 
        url = 'readonly+' + backing_transport_server.get_url()
321
 
        return transport.get_transport(url)
322
 
 
323
 
 
324
266
def _local_path_for_transport(transport):
325
267
    """Return a local path for transport, if reasonably possible.
326
268
    
388
330
        chroot_server = chroot.ChrootServer(transport)
389
331
        chroot_server.start_server()
390
332
        self.cleanups.append(chroot_server.stop_server)
391
 
        transport = get_transport(chroot_server.get_url())
 
333
        transport = _mod_transport.get_transport_from_url(chroot_server.get_url())
392
334
        if self.base_path is not None:
393
335
            # Decorate the server's backing transport with a filter that can
394
336
            # expand homedirs.
395
337
            expand_userdirs = self._make_expand_userdirs_filter(transport)
396
338
            expand_userdirs.start_server()
397
339
            self.cleanups.append(expand_userdirs.stop_server)
398
 
            transport = get_transport(expand_userdirs.get_url())
 
340
            transport = _mod_transport.get_transport_from_url(expand_userdirs.get_url())
399
341
        self.transport = transport
400
342
 
401
343
    def _make_smart_server(self, host, port, inet):
407
349
                host = medium.BZR_DEFAULT_INTERFACE
408
350
            if port is None:
409
351
                port = medium.BZR_DEFAULT_PORT
410
 
            smart_server = SmartTCPServer(self.transport, host=host, port=port)
 
352
            smart_server = SmartTCPServer(self.transport)
 
353
            smart_server.start_server(host, port)
411
354
            trace.note('listening on port: %s' % smart_server.port)
412
355
        self.smart_server = smart_server
413
356
 
435
378
        for cleanup in reversed(self.cleanups):
436
379
            cleanup()
437
380
 
438
 
 
439
381
def serve_bzr(transport, host=None, port=None, inet=False):
440
382
    """This is the default implementation of 'bzr serve'.
441
383
    
447
389
    try:
448
390
        bzr_server.set_up(transport, host, port, inet)
449
391
        bzr_server.smart_server.serve()
 
392
    except:
 
393
        hook_caught_exception = False
 
394
        for hook in SmartTCPServer.hooks['server_exception']:
 
395
            hook_caught_exception = hook(sys.exc_info())
 
396
        if not hook_caught_exception:
 
397
            raise
450
398
    finally:
451
399
        bzr_server.tear_down()
452