~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/smart/server.py

  • Committer: John Arbash Meinel
  • Date: 2010-02-17 17:11:16 UTC
  • mfrom: (4797.2.17 2.1)
  • mto: (4797.2.18 2.1)
  • mto: This revision was merged to the branch mainline in revision 5055.
  • Revision ID: john@arbash-meinel.com-20100217171116-h7t9223ystbnx5h8
merge bzr.2.1 in preparation for NEWS entry.

Show diffs side-by-side

added added

removed removed

Lines of Context:
18
18
 
19
19
import errno
20
20
import os.path
21
 
import select
22
21
import socket
23
22
import sys
24
23
import threading
52
51
    hooks: An instance of SmartServerHooks.
53
52
    """
54
53
 
55
 
    def __init__(self, backing_transport, root_client_path='/'):
 
54
    def __init__(self, backing_transport, host='127.0.0.1', port=0,
 
55
                 root_client_path='/'):
56
56
        """Construct a new server.
57
57
 
58
58
        To actually start it running, call either start_background_thread or
59
59
        serve.
60
60
 
61
61
        :param backing_transport: The transport to serve.
 
62
        :param host: Name of the interface to listen on.
 
63
        :param port: TCP port to listen on, or 0 to allocate a transient port.
62
64
        :param root_client_path: The client path that will correspond to root
63
65
            of backing_transport.
64
66
        """
65
 
        self.backing_transport = backing_transport
66
 
        self.root_client_path = root_client_path
67
 
 
68
 
    def start_server(self, host, port):
69
 
        """Create the server listening socket.
70
 
 
71
 
        :param host: Name of the interface to listen on.
72
 
        :param port: TCP port to listen on, or 0 to allocate a transient port.
73
 
        """
74
67
        # let connections timeout so that we get a chance to terminate
75
68
        # Keep a reference to the exceptions we want to catch because the socket
76
69
        # module's globals get set to None during interpreter shutdown.
96
89
        self.port = self._sockname[1]
97
90
        self._server_socket.listen(1)
98
91
        self._server_socket.settimeout(1)
 
92
        self.backing_transport = backing_transport
99
93
        self._started = threading.Event()
100
94
        self._stopped = threading.Event()
 
95
        self.root_client_path = root_client_path
101
96
 
102
 
    def _backing_urls(self):
 
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).
103
101
        # There are three interesting urls:
104
102
        # The URL the server can be contacted on. (e.g. bzr://host/)
105
103
        # The URL that a commit done on the same machine as the server will
115
113
        # The latter two urls are different aliases to the servers url,
116
114
        # so we group those in a list - as there might be more aliases
117
115
        # in the future.
118
 
        urls = [self.backing_transport.base]
 
116
        backing_urls = [self.backing_transport.base]
119
117
        try:
120
 
            urls.append(self.backing_transport.external_url())
 
118
            backing_urls.append(self.backing_transport.external_url())
121
119
        except errors.InProcessTransport:
122
120
            pass
123
 
        return urls
124
 
 
125
 
    def run_server_started_hooks(self, backing_urls=None):
126
 
        if backing_urls is None:
127
 
            backing_urls = self._backing_urls()
128
121
        for hook in SmartTCPServer.hooks['server_started']:
129
122
            hook(backing_urls, self.get_url())
130
123
        for hook in SmartTCPServer.hooks['server_started_ex']:
131
124
            hook(backing_urls, self)
132
 
 
133
 
    def run_server_stopped_hooks(self, backing_urls=None):
134
 
        if backing_urls is None:
135
 
            backing_urls = self._backing_urls()
136
 
        for hook in SmartTCPServer.hooks['server_stopped']:
137
 
            hook(backing_urls, self.get_url())
138
 
 
139
 
    def serve(self, thread_name_suffix=''):
140
 
        self._should_terminate = False
141
 
        # for hooks we are letting code know that a server has started (and
142
 
        # later stopped).
143
 
        self.run_server_started_hooks()
144
125
        self._started.set()
145
126
        try:
146
127
            try:
157
138
                        if e.args[0] != errno.EBADF:
158
139
                            trace.warning("listening socket error: %s", e)
159
140
                    else:
160
 
                        if self._should_terminate:
161
 
                            break
162
141
                        self.serve_conn(conn, thread_name_suffix)
163
142
            except KeyboardInterrupt:
164
143
                # dont log when CTRL-C'd.
174
153
            except self._socket_error:
175
154
                # ignore errors on close
176
155
                pass
177
 
            self.run_server_stopped_hooks()
 
156
            for hook in SmartTCPServer.hooks['server_stopped']:
 
157
                hook(backing_urls, self.get_url())
178
158
 
179
159
    def get_url(self):
180
160
        """Return the url of the server"""
190
170
        thread_name = 'smart-server-child' + thread_name_suffix
191
171
        connection_thread = threading.Thread(
192
172
            None, handler.serve, name=thread_name)
193
 
        # FIXME: This thread is never joined, it should at least be collected
194
 
        # somewhere so that tests that want to check for leaked threads can get
195
 
        # rid of them -- vila 20100531
196
173
        connection_thread.setDaemon(True)
197
174
        connection_thread.start()
198
 
        return connection_thread
199
175
 
200
176
    def start_background_thread(self, thread_name_suffix=''):
201
177
        self._started.clear()
260
236
SmartTCPServer.hooks = SmartServerHooks()
261
237
 
262
238
 
 
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
 
263
324
def _local_path_for_transport(transport):
264
325
    """Return a local path for transport, if reasonably possible.
265
326
    
346
407
                host = medium.BZR_DEFAULT_INTERFACE
347
408
            if port is None:
348
409
                port = medium.BZR_DEFAULT_PORT
349
 
            smart_server = SmartTCPServer(self.transport)
350
 
            smart_server.start_server(host, port)
 
410
            smart_server = SmartTCPServer(self.transport, host=host, port=port)
351
411
            trace.note('listening on port: %s' % smart_server.port)
352
412
        self.smart_server = smart_server
353
413