/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/request.py

  • Committer: John Arbash Meinel
  • Date: 2008-11-25 18:51:48 UTC
  • mto: This revision was merged to the branch mainline in revision 3854.
  • Revision ID: john@arbash-meinel.com-20081125185148-jsfkqnzfjjqsleds
It seems we have some direct tests that don't use strings and expect a value error as well.

They would be sanitized later on by Revision. We could use that code, but this test
depends on the serializer, which Revision wouldn't know about.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# Copyright (C) 2006, 2007 Canonical Ltd
 
2
#
 
3
# This program is free software; you can redistribute it and/or modify
 
4
# it under the terms of the GNU General Public License as published by
 
5
# the Free Software Foundation; either version 2 of the License, or
 
6
# (at your option) any later version.
 
7
#
 
8
# This program is distributed in the hope that it will be useful,
 
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
 
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
11
# GNU General Public License for more details.
 
12
#
 
13
# You should have received a copy of the GNU General Public License
 
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
 
16
 
 
17
"""Basic server-side logic for dealing with requests.
 
18
 
 
19
**XXX**:
 
20
 
 
21
The class names are a little confusing: the protocol will instantiate a
 
22
SmartServerRequestHandler, whose dispatch_command method creates an instance of
 
23
a SmartServerRequest subclass.
 
24
 
 
25
The request_handlers registry tracks SmartServerRequest classes (rather than
 
26
SmartServerRequestHandler).
 
27
"""
 
28
 
 
29
import tempfile
 
30
 
 
31
from bzrlib import (
 
32
    bzrdir,
 
33
    errors,
 
34
    registry,
 
35
    revision,
 
36
    urlutils,
 
37
    )
 
38
from bzrlib.lazy_import import lazy_import
 
39
lazy_import(globals(), """
 
40
from bzrlib.bundle import serializer
 
41
""")
 
42
 
 
43
 
 
44
class SmartServerRequest(object):
 
45
    """Base class for request handlers.
 
46
    
 
47
    To define a new request, subclass this class and override the `do` method
 
48
    (and if appropriate, `do_body` as well).  Request implementors should take
 
49
    care to call `translate_client_path` and `transport_from_client_path` as
 
50
    appropriate when dealing with paths received from the client.
 
51
    """
 
52
    # XXX: rename this class to BaseSmartServerRequestHandler ?  A request
 
53
    # *handler* is a different concept to the request.
 
54
 
 
55
    def __init__(self, backing_transport, root_client_path='/'):
 
56
        """Constructor.
 
57
 
 
58
        :param backing_transport: the base transport to be used when performing
 
59
            this request.
 
60
        :param root_client_path: the client path that maps to the root of
 
61
            backing_transport.  This is used to interpret relpaths received
 
62
            from the client.  Clients will not be able to refer to paths above
 
63
            this root.  If root_client_path is None, then no translation will
 
64
            be performed on client paths.  Default is '/'.
 
65
        """
 
66
        self._backing_transport = backing_transport
 
67
        if root_client_path is not None:
 
68
            if not root_client_path.startswith('/'):
 
69
                root_client_path = '/' + root_client_path
 
70
            if not root_client_path.endswith('/'):
 
71
                root_client_path += '/'
 
72
        self._root_client_path = root_client_path
 
73
 
 
74
    def _check_enabled(self):
 
75
        """Raises DisabledMethod if this method is disabled."""
 
76
        pass
 
77
 
 
78
    def do(self, *args):
 
79
        """Mandatory extension point for SmartServerRequest subclasses.
 
80
        
 
81
        Subclasses must implement this.
 
82
        
 
83
        This should return a SmartServerResponse if this command expects to
 
84
        receive no body.
 
85
        """
 
86
        raise NotImplementedError(self.do)
 
87
 
 
88
    def execute(self, *args):
 
89
        """Public entry point to execute this request.
 
90
 
 
91
        It will return a SmartServerResponse if the command does not expect a
 
92
        body.
 
93
 
 
94
        :param *args: the arguments of the request.
 
95
        """
 
96
        self._check_enabled()
 
97
        return self.do(*args)
 
98
 
 
99
    def do_body(self, body_bytes):
 
100
        """Called if the client sends a body with the request.
 
101
 
 
102
        The do() method is still called, and must have returned None.
 
103
        
 
104
        Must return a SmartServerResponse.
 
105
        """
 
106
        raise NotImplementedError(self.do_body)
 
107
 
 
108
    def do_chunk(self, chunk_bytes):
 
109
        """Called with each body chunk if the request has a streamed body.
 
110
 
 
111
        The do() method is still called, and must have returned None.
 
112
        """
 
113
        raise NotImplementedError(self.do_chunk)
 
114
 
 
115
    def do_end(self):
 
116
        """Called when the end of the request has been received."""
 
117
        pass
 
118
    
 
119
    def translate_client_path(self, client_path):
 
120
        """Translate a path received from a network client into a local
 
121
        relpath.
 
122
 
 
123
        All paths received from the client *must* be translated.
 
124
 
 
125
        :param client_path: the path from the client.
 
126
        :returns: a relpath that may be used with self._backing_transport
 
127
            (unlike the untranslated client_path, which must not be used with
 
128
            the backing transport).
 
129
        """
 
130
        if self._root_client_path is None:
 
131
            # no translation necessary!
 
132
            return client_path
 
133
        if not client_path.startswith('/'):
 
134
            client_path = '/' + client_path
 
135
        if client_path.startswith(self._root_client_path):
 
136
            path = client_path[len(self._root_client_path):]
 
137
            relpath = urlutils.joinpath('/', path)
 
138
            if not relpath.startswith('/'):
 
139
                raise ValueError(relpath)
 
140
            return '.' + relpath
 
141
        else:
 
142
            raise errors.PathNotChild(client_path, self._root_client_path)
 
143
 
 
144
    def transport_from_client_path(self, client_path):
 
145
        """Get a backing transport corresponding to the location referred to by
 
146
        a network client.
 
147
 
 
148
        :seealso: translate_client_path
 
149
        :returns: a transport cloned from self._backing_transport
 
150
        """
 
151
        relpath = self.translate_client_path(client_path)
 
152
        return self._backing_transport.clone(relpath)
 
153
 
 
154
 
 
155
class SmartServerResponse(object):
 
156
    """A response to a client request.
 
157
    
 
158
    This base class should not be used. Instead use
 
159
    SuccessfulSmartServerResponse and FailedSmartServerResponse as appropriate.
 
160
    """
 
161
 
 
162
    def __init__(self, args, body=None, body_stream=None):
 
163
        """Constructor.
 
164
 
 
165
        :param args: tuple of response arguments.
 
166
        :param body: string of a response body.
 
167
        :param body_stream: iterable of bytestrings to be streamed to the
 
168
            client.
 
169
        """
 
170
        self.args = args
 
171
        if body is not None and body_stream is not None:
 
172
            raise errors.BzrError(
 
173
                "'body' and 'body_stream' are mutually exclusive.")
 
174
        self.body = body
 
175
        self.body_stream = body_stream
 
176
 
 
177
    def __eq__(self, other):
 
178
        if other is None:
 
179
            return False
 
180
        return (other.args == self.args and
 
181
                other.body == self.body and
 
182
                other.body_stream is self.body_stream)
 
183
 
 
184
    def __repr__(self):
 
185
        return "<%s args=%r body=%r>" % (self.__class__.__name__,
 
186
            self.args, self.body)
 
187
 
 
188
 
 
189
class FailedSmartServerResponse(SmartServerResponse):
 
190
    """A SmartServerResponse for a request which failed."""
 
191
 
 
192
    def is_successful(self):
 
193
        """FailedSmartServerResponse are not successful."""
 
194
        return False
 
195
 
 
196
 
 
197
class SuccessfulSmartServerResponse(SmartServerResponse):
 
198
    """A SmartServerResponse for a successfully completed request."""
 
199
 
 
200
    def is_successful(self):
 
201
        """SuccessfulSmartServerResponse are successful."""
 
202
        return True
 
203
 
 
204
 
 
205
class SmartServerRequestHandler(object):
 
206
    """Protocol logic for smart server.
 
207
    
 
208
    This doesn't handle serialization at all, it just processes requests and
 
209
    creates responses.
 
210
    """
 
211
 
 
212
    # IMPORTANT FOR IMPLEMENTORS: It is important that SmartServerRequestHandler
 
213
    # not contain encoding or decoding logic to allow the wire protocol to vary
 
214
    # from the object protocol: we will want to tweak the wire protocol separate
 
215
    # from the object model, and ideally we will be able to do that without
 
216
    # having a SmartServerRequestHandler subclass for each wire protocol, rather
 
217
    # just a Protocol subclass.
 
218
 
 
219
    # TODO: Better way of representing the body for commands that take it,
 
220
    # and allow it to be streamed into the server.
 
221
 
 
222
    def __init__(self, backing_transport, commands, root_client_path):
 
223
        """Constructor.
 
224
 
 
225
        :param backing_transport: a Transport to handle requests for.
 
226
        :param commands: a registry mapping command names to SmartServerRequest
 
227
            subclasses. e.g. bzrlib.transport.smart.vfs.vfs_commands.
 
228
        """
 
229
        self._backing_transport = backing_transport
 
230
        self._root_client_path = root_client_path
 
231
        self._commands = commands
 
232
        self._body_bytes = ''
 
233
        self.response = None
 
234
        self.finished_reading = False
 
235
        self._command = None
 
236
 
 
237
    def accept_body(self, bytes):
 
238
        """Accept body data."""
 
239
 
 
240
        # TODO: This should be overriden for each command that desired body data
 
241
        # to handle the right format of that data, i.e. plain bytes, a bundle,
 
242
        # etc.  The deserialisation into that format should be done in the
 
243
        # Protocol object.
 
244
 
 
245
        # default fallback is to accumulate bytes.
 
246
        self._body_bytes += bytes
 
247
        
 
248
    def end_of_body(self):
 
249
        """No more body data will be received."""
 
250
        self._run_handler_code(self._command.do_body, (self._body_bytes,), {})
 
251
        # cannot read after this.
 
252
        self.finished_reading = True
 
253
 
 
254
    def dispatch_command(self, cmd, args):
 
255
        """Deprecated compatibility method.""" # XXX XXX
 
256
        try:
 
257
            command = self._commands.get(cmd)
 
258
        except LookupError:
 
259
            raise errors.UnknownSmartMethod(cmd)
 
260
        self._command = command(self._backing_transport, self._root_client_path)
 
261
        self._run_handler_code(self._command.execute, args, {})
 
262
 
 
263
    def _run_handler_code(self, callable, args, kwargs):
 
264
        """Run some handler specific code 'callable'.
 
265
 
 
266
        If a result is returned, it is considered to be the commands response,
 
267
        and finished_reading is set true, and its assigned to self.response.
 
268
 
 
269
        Any exceptions caught are translated and a response object created
 
270
        from them.
 
271
        """
 
272
        result = self._call_converting_errors(callable, args, kwargs)
 
273
 
 
274
        if result is not None:
 
275
            self.response = result
 
276
            self.finished_reading = True
 
277
 
 
278
    def _call_converting_errors(self, callable, args, kwargs):
 
279
        """Call callable converting errors to Response objects."""
 
280
        # XXX: most of this error conversion is VFS-related, and thus ought to
 
281
        # be in SmartServerVFSRequestHandler somewhere.
 
282
        try:
 
283
            return callable(*args, **kwargs)
 
284
        except errors.NoSuchFile, e:
 
285
            return FailedSmartServerResponse(('NoSuchFile', e.path))
 
286
        except errors.FileExists, e:
 
287
            return FailedSmartServerResponse(('FileExists', e.path))
 
288
        except errors.DirectoryNotEmpty, e:
 
289
            return FailedSmartServerResponse(('DirectoryNotEmpty', e.path))
 
290
        except errors.ShortReadvError, e:
 
291
            return FailedSmartServerResponse(('ShortReadvError',
 
292
                e.path, str(e.offset), str(e.length), str(e.actual)))
 
293
        except errors.UnstackableRepositoryFormat, e:
 
294
            return FailedSmartServerResponse(('UnstackableRepositoryFormat',
 
295
                str(e.format), e.url))
 
296
        except errors.UnstackableBranchFormat, e:
 
297
            return FailedSmartServerResponse(('UnstackableBranchFormat',
 
298
                str(e.format), e.url))
 
299
        except errors.NotStacked, e:
 
300
            return FailedSmartServerResponse(('NotStacked',))
 
301
        except UnicodeError, e:
 
302
            # If it is a DecodeError, than most likely we are starting
 
303
            # with a plain string
 
304
            str_or_unicode = e.object
 
305
            if isinstance(str_or_unicode, unicode):
 
306
                # XXX: UTF-8 might have \x01 (our protocol v1 and v2 seperator
 
307
                # byte) in it, so this encoding could cause broken responses.
 
308
                # Newer clients use protocol v3, so will be fine.
 
309
                val = 'u:' + str_or_unicode.encode('utf-8')
 
310
            else:
 
311
                val = 's:' + str_or_unicode.encode('base64')
 
312
            # This handles UnicodeEncodeError or UnicodeDecodeError
 
313
            return FailedSmartServerResponse((e.__class__.__name__,
 
314
                    e.encoding, val, str(e.start), str(e.end), e.reason))
 
315
        except errors.TransportNotPossible, e:
 
316
            if e.msg == "readonly transport":
 
317
                return FailedSmartServerResponse(('ReadOnlyError', ))
 
318
            else:
 
319
                raise
 
320
        except errors.ReadError, e:
 
321
            # cannot read the file
 
322
            return FailedSmartServerResponse(('ReadError', e.path))
 
323
        except errors.PermissionDenied, e:
 
324
            return FailedSmartServerResponse(
 
325
                ('PermissionDenied', e.path, e.extra))
 
326
 
 
327
    def headers_received(self, headers):
 
328
        # Just a no-op at the moment.
 
329
        pass
 
330
 
 
331
    def args_received(self, args):
 
332
        cmd = args[0]
 
333
        args = args[1:]
 
334
        try:
 
335
            command = self._commands.get(cmd)
 
336
        except LookupError:
 
337
            raise errors.UnknownSmartMethod(cmd)
 
338
        self._command = command(self._backing_transport)
 
339
        self._run_handler_code(self._command.execute, args, {})
 
340
 
 
341
    def prefixed_body_received(self, body_bytes):
 
342
        """No more body data will be received."""
 
343
        self._run_handler_code(self._command.do_body, (body_bytes,), {})
 
344
        # cannot read after this.
 
345
        self.finished_reading = True
 
346
 
 
347
    def body_chunk_received(self, chunk_bytes):
 
348
        self._run_handler_code(self._command.do_chunk, (chunk_bytes,), {})
 
349
 
 
350
    def end_received(self):
 
351
        self._run_handler_code(self._command.do_end, (), {})
 
352
 
 
353
 
 
354
class HelloRequest(SmartServerRequest):
 
355
    """Answer a version request with the highest protocol version this server
 
356
    supports.
 
357
    """
 
358
 
 
359
    def do(self):
 
360
        return SuccessfulSmartServerResponse(('ok', '2'))
 
361
 
 
362
 
 
363
class GetBundleRequest(SmartServerRequest):
 
364
    """Get a bundle of from the null revision to the specified revision."""
 
365
 
 
366
    def do(self, path, revision_id):
 
367
        # open transport relative to our base
 
368
        t = self.transport_from_client_path(path)
 
369
        control, extra_path = bzrdir.BzrDir.open_containing_from_transport(t)
 
370
        repo = control.open_repository()
 
371
        tmpf = tempfile.TemporaryFile()
 
372
        base_revision = revision.NULL_REVISION
 
373
        serializer.write_bundle(repo, revision_id, base_revision, tmpf)
 
374
        tmpf.seek(0)
 
375
        return SuccessfulSmartServerResponse((), tmpf.read())
 
376
 
 
377
 
 
378
class SmartServerIsReadonly(SmartServerRequest):
 
379
    # XXX: this request method belongs somewhere else.
 
380
 
 
381
    def do(self):
 
382
        if self._backing_transport.is_readonly():
 
383
            answer = 'yes'
 
384
        else:
 
385
            answer = 'no'
 
386
        return SuccessfulSmartServerResponse((answer,))
 
387
 
 
388
 
 
389
request_handlers = registry.Registry()
 
390
request_handlers.register_lazy(
 
391
    'append', 'bzrlib.smart.vfs', 'AppendRequest')
 
392
request_handlers.register_lazy(
 
393
    'Branch.get_config_file', 'bzrlib.smart.branch', 'SmartServerBranchGetConfigFile')
 
394
request_handlers.register_lazy(
 
395
    'Branch.get_stacked_on_url', 'bzrlib.smart.branch', 'SmartServerBranchRequestGetStackedOnURL')
 
396
request_handlers.register_lazy(
 
397
    'Branch.last_revision_info', 'bzrlib.smart.branch', 'SmartServerBranchRequestLastRevisionInfo')
 
398
request_handlers.register_lazy(
 
399
    'Branch.lock_write', 'bzrlib.smart.branch', 'SmartServerBranchRequestLockWrite')
 
400
request_handlers.register_lazy(
 
401
    'Branch.revision_history', 'bzrlib.smart.branch', 'SmartServerRequestRevisionHistory')
 
402
request_handlers.register_lazy(
 
403
    'Branch.set_last_revision', 'bzrlib.smart.branch', 'SmartServerBranchRequestSetLastRevision')
 
404
request_handlers.register_lazy(
 
405
    'Branch.set_last_revision_info', 'bzrlib.smart.branch',
 
406
    'SmartServerBranchRequestSetLastRevisionInfo')
 
407
request_handlers.register_lazy(
 
408
    'Branch.set_last_revision_ex', 'bzrlib.smart.branch',
 
409
    'SmartServerBranchRequestSetLastRevisionEx')
 
410
request_handlers.register_lazy(
 
411
    'Branch.unlock', 'bzrlib.smart.branch', 'SmartServerBranchRequestUnlock')
 
412
request_handlers.register_lazy(
 
413
    'BzrDir.find_repository', 'bzrlib.smart.bzrdir', 'SmartServerRequestFindRepositoryV1')
 
414
request_handlers.register_lazy(
 
415
    'BzrDir.find_repositoryV2', 'bzrlib.smart.bzrdir', 'SmartServerRequestFindRepositoryV2')
 
416
request_handlers.register_lazy(
 
417
    'BzrDirFormat.initialize', 'bzrlib.smart.bzrdir', 'SmartServerRequestInitializeBzrDir')
 
418
request_handlers.register_lazy(
 
419
    'BzrDir.open_branch', 'bzrlib.smart.bzrdir', 'SmartServerRequestOpenBranch')
 
420
request_handlers.register_lazy(
 
421
    'delete', 'bzrlib.smart.vfs', 'DeleteRequest')
 
422
request_handlers.register_lazy(
 
423
    'get', 'bzrlib.smart.vfs', 'GetRequest')
 
424
request_handlers.register_lazy(
 
425
    'get_bundle', 'bzrlib.smart.request', 'GetBundleRequest')
 
426
request_handlers.register_lazy(
 
427
    'has', 'bzrlib.smart.vfs', 'HasRequest')
 
428
request_handlers.register_lazy(
 
429
    'hello', 'bzrlib.smart.request', 'HelloRequest')
 
430
request_handlers.register_lazy(
 
431
    'iter_files_recursive', 'bzrlib.smart.vfs', 'IterFilesRecursiveRequest')
 
432
request_handlers.register_lazy(
 
433
    'list_dir', 'bzrlib.smart.vfs', 'ListDirRequest')
 
434
request_handlers.register_lazy(
 
435
    'mkdir', 'bzrlib.smart.vfs', 'MkdirRequest')
 
436
request_handlers.register_lazy(
 
437
    'move', 'bzrlib.smart.vfs', 'MoveRequest')
 
438
request_handlers.register_lazy(
 
439
    'put', 'bzrlib.smart.vfs', 'PutRequest')
 
440
request_handlers.register_lazy(
 
441
    'put_non_atomic', 'bzrlib.smart.vfs', 'PutNonAtomicRequest')
 
442
request_handlers.register_lazy(
 
443
    'readv', 'bzrlib.smart.vfs', 'ReadvRequest')
 
444
request_handlers.register_lazy(
 
445
    'rename', 'bzrlib.smart.vfs', 'RenameRequest')
 
446
request_handlers.register_lazy(
 
447
    'PackRepository.autopack', 'bzrlib.smart.packrepository',
 
448
    'SmartServerPackRepositoryAutopack')
 
449
request_handlers.register_lazy('Repository.gather_stats',
 
450
                               'bzrlib.smart.repository',
 
451
                               'SmartServerRepositoryGatherStats')
 
452
request_handlers.register_lazy('Repository.get_parent_map',
 
453
                               'bzrlib.smart.repository',
 
454
                               'SmartServerRepositoryGetParentMap')
 
455
request_handlers.register_lazy(
 
456
    'Repository.get_revision_graph', 'bzrlib.smart.repository', 'SmartServerRepositoryGetRevisionGraph')
 
457
request_handlers.register_lazy(
 
458
    'Repository.has_revision', 'bzrlib.smart.repository', 'SmartServerRequestHasRevision')
 
459
request_handlers.register_lazy(
 
460
    'Repository.is_shared', 'bzrlib.smart.repository', 'SmartServerRepositoryIsShared')
 
461
request_handlers.register_lazy(
 
462
    'Repository.lock_write', 'bzrlib.smart.repository', 'SmartServerRepositoryLockWrite')
 
463
request_handlers.register_lazy(
 
464
    'Repository.unlock', 'bzrlib.smart.repository', 'SmartServerRepositoryUnlock')
 
465
request_handlers.register_lazy(
 
466
    'Repository.tarball', 'bzrlib.smart.repository',
 
467
    'SmartServerRepositoryTarball')
 
468
request_handlers.register_lazy(
 
469
    'rmdir', 'bzrlib.smart.vfs', 'RmdirRequest')
 
470
request_handlers.register_lazy(
 
471
    'stat', 'bzrlib.smart.vfs', 'StatRequest')
 
472
request_handlers.register_lazy(
 
473
    'Transport.is_readonly', 'bzrlib.smart.request', 'SmartServerIsReadonly')
 
474
request_handlers.register_lazy(
 
475
    'BzrDir.open', 'bzrlib.smart.bzrdir', 'SmartServerRequestOpenBzrDir')