~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/smart/server.py

  • Committer: Kuno Meyer
  • Date: 2007-07-24 19:40:40 UTC
  • mto: This revision was merged to the branch mainline in revision 2655.
  • Revision ID: kuno.meyer@gmx.ch-20070724194040-ocyjulqhy31xe3j1
Extended tests for unicode chars outside of the iso-8859-* range
Two workarounds for incorrect glob.glob() implementation

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2006, 2007, 2008 Canonical Ltd
 
1
# Copyright (C) 2006, 2007 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
 
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
16
16
 
17
17
"""Server for smart-server protocol."""
18
18
 
19
19
import errno
20
20
import socket
21
 
import sys
22
21
import threading
23
22
 
24
 
from bzrlib.hooks import HookPoint, Hooks
 
23
from bzrlib.hooks import Hooks
25
24
from bzrlib import (
26
 
    errors,
27
25
    trace,
28
26
    transport,
29
27
)
30
 
from bzrlib.lazy_import import lazy_import
31
 
lazy_import(globals(), """
32
 
from bzrlib.smart import medium
33
 
""")
 
28
from bzrlib.smart.medium import SmartServerSocketStreamMedium
34
29
 
35
30
 
36
31
class SmartTCPServer(object):
42
37
    hooks: An instance of SmartServerHooks.
43
38
    """
44
39
 
45
 
    def __init__(self, backing_transport, host='127.0.0.1', port=0,
46
 
                 root_client_path='/'):
 
40
    def __init__(self, backing_transport, host='127.0.0.1', port=0):
47
41
        """Construct a new server.
48
42
 
49
43
        To actually start it running, call either start_background_thread or
50
44
        serve.
51
45
 
52
 
        :param backing_transport: The transport to serve.
53
46
        :param host: Name of the interface to listen on.
54
47
        :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.
57
48
        """
58
49
        # let connections timeout so that we get a chance to terminate
59
50
        # Keep a reference to the exceptions we want to catch because the socket
62
53
        from socket import error as socket_error
63
54
        self._socket_error = socket_error
64
55
        self._socket_timeout = socket_timeout
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)
 
56
        self._server_socket = socket.socket()
 
57
        self._server_socket.bind((host, port))
79
58
        self._sockname = self._server_socket.getsockname()
80
59
        self.port = self._sockname[1]
81
60
        self._server_socket.listen(1)
83
62
        self.backing_transport = backing_transport
84
63
        self._started = threading.Event()
85
64
        self._stopped = threading.Event()
86
 
        self.root_client_path = root_client_path
87
65
 
88
 
    def serve(self, thread_name_suffix=''):
 
66
    def serve(self):
89
67
        self._should_terminate = False
90
 
        # for hooks we are letting code know that a server has started (and
91
 
        # later stopped).
92
 
        # There are three interesting urls:
93
 
        # The URL the server can be contacted on. (e.g. bzr://host/)
94
 
        # The URL that a commit done on the same machine as the server will
95
 
        # have within the servers space. (e.g. file:///home/user/source)
96
 
        # The URL that will be given to other hooks in the same process -
97
 
        # the URL of the backing transport itself. (e.g. chroot+:///)
98
 
        # We need all three because:
99
 
        #  * other machines see the first
100
 
        #  * local commits on this machine should be able to be mapped to
101
 
        #    this server
102
 
        #  * commits the server does itself need to be mapped across to this
103
 
        #    server.
104
 
        # The latter two urls are different aliases to the servers url,
105
 
        # so we group those in a list - as there might be more aliases
106
 
        # in the future.
107
 
        backing_urls = [self.backing_transport.base]
108
 
        try:
109
 
            backing_urls.append(self.backing_transport.external_url())
110
 
        except errors.InProcessTransport:
111
 
            pass
112
68
        for hook in SmartTCPServer.hooks['server_started']:
113
 
            hook(backing_urls, self.get_url())
 
69
            hook(self.backing_transport.base, self.get_url())
114
70
        self._started.set()
115
71
        try:
116
72
            try:
127
83
                        if e.args[0] != errno.EBADF:
128
84
                            trace.warning("listening socket error: %s", e)
129
85
                    else:
130
 
                        self.serve_conn(conn, thread_name_suffix)
 
86
                        self.serve_conn(conn)
131
87
            except KeyboardInterrupt:
132
88
                # dont log when CTRL-C'd.
133
89
                raise
144
100
                # ignore errors on close
145
101
                pass
146
102
            for hook in SmartTCPServer.hooks['server_stopped']:
147
 
                hook(backing_urls, self.get_url())
 
103
                hook(self.backing_transport.base, self.get_url())
148
104
 
149
105
    def get_url(self):
150
106
        """Return the url of the server"""
151
107
        return "bzr://%s:%d/" % self._sockname
152
108
 
153
 
    def serve_conn(self, conn, thread_name_suffix):
 
109
    def serve_conn(self, conn):
154
110
        # For WIN32, where the timeout value from the listening socket
155
 
        # propagates to the newly accepted socket.
 
111
        # propogates to the newly accepted socket.
156
112
        conn.setblocking(True)
157
113
        conn.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
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)
 
114
        handler = SmartServerSocketStreamMedium(conn, self.backing_transport)
 
115
        connection_thread = threading.Thread(None, handler.serve, name='smart-server-child')
163
116
        connection_thread.setDaemon(True)
164
117
        connection_thread.start()
165
118
 
166
 
    def start_background_thread(self, thread_name_suffix=''):
 
119
    def start_background_thread(self):
167
120
        self._started.clear()
168
121
        self._server_thread = threading.Thread(None,
169
 
                self.serve, args=(thread_name_suffix,),
 
122
                self.serve,
170
123
                name='server-' + self.get_url())
171
124
        self._server_thread.setDaemon(True)
172
125
        self._server_thread.start()
178
131
        self._should_terminate = True
179
132
        # close the socket - gives error to connections from here on in,
180
133
        # rather than a connection reset error to connections made during
181
 
        # the period between setting _should_terminate = True and
 
134
        # the period between setting _should_terminate = True and 
182
135
        # the current request completing/aborting. It may also break out the
183
136
        # main loop if it was currently in accept() (on some platforms).
184
137
        try:
208
161
        notified.
209
162
        """
210
163
        Hooks.__init__(self)
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))
 
164
        # Introduced in 0.16:
 
165
        # invoked whenever the server starts serving a directory.
 
166
        # The api signature is (backing url, public url).
 
167
        self['server_started'] = []
 
168
        # Introduced in 0.16:
 
169
        # invoked whenever the server stops serving a directory.
 
170
        # The api signature is (backing url, public url).
 
171
        self['server_stopped'] = []
221
172
 
222
173
SmartTCPServer.hooks = SmartServerHooks()
223
174
 
224
175
 
225
176
class SmartTCPServer_for_testing(SmartTCPServer):
226
177
    """Server suitable for use by transport tests.
227
 
 
 
178
    
228
179
    This server is backed by the process's cwd.
229
180
    """
230
181
 
231
 
    def __init__(self, thread_name_suffix=''):
 
182
    def __init__(self):
232
183
        SmartTCPServer.__init__(self, None)
233
 
        self.client_path_extra = None
234
 
        self.thread_name_suffix = thread_name_suffix
235
 
 
 
184
        
236
185
    def get_backing_transport(self, backing_transport_server):
237
186
        """Get a backing transport from a server we are decorating."""
238
187
        return transport.get_transport(backing_transport_server.get_url())
239
188
 
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)
 
189
    def setUp(self, backing_transport_server=None):
 
190
        """Set up server for testing"""
256
191
        from bzrlib.transport.chroot import ChrootServer
257
192
        if backing_transport_server is None:
258
193
            from bzrlib.transport.local import LocalURLServer
262
197
        self.chroot_server.setUp()
263
198
        self.backing_transport = transport.get_transport(
264
199
            self.chroot_server.get_url())
265
 
        self.root_client_path = self.client_path_extra = client_path_extra
266
 
        self.start_background_thread(self.thread_name_suffix)
 
200
        self.start_background_thread()
267
201
 
268
202
    def tearDown(self):
269
203
        self.stop_background_thread()
270
204
        self.chroot_server.tearDown()
271
205
 
272
 
    def get_url(self):
273
 
        url = super(SmartTCPServer_for_testing, self).get_url()
274
 
        return url[:-1] + self.client_path_extra
275
 
 
276
206
    def get_bogus_url(self):
277
207
        """Return a URL which will fail to connect"""
278
208
        return 'bzr://127.0.0.1:1/'
286
216
        url = 'readonly+' + backing_transport_server.get_url()
287
217
        return transport.get_transport(url)
288
218
 
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