~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/smart/request.py

  • Committer: Canonical.com Patch Queue Manager
  • Date: 2009-08-27 02:27:19 UTC
  • mfrom: (4634.3.19 gc-batching)
  • Revision ID: pqm@pqm.ubuntu.com-20090827022719-bl2yoqhpj3fcfczu
(andrew) Fix #402657: 2a fetch over dumb transport reads one group at
        a time.

Show diffs side-by-side

added added

removed removed

Lines of Context:
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
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).
 
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
 
16
 
 
17
"""Infrastructure for server-side request handlers.
 
18
 
 
19
Interesting module attributes:
 
20
    * The request_handlers registry maps verb names to SmartServerRequest
 
21
      classes.
 
22
    * The jail_info threading.local() object is used to prevent accidental
 
23
      opening of BzrDirs outside of the backing transport, or any other
 
24
      transports placed in jail_info.transports.  The jail_info is reset on
 
25
      every call into a request handler (which can happen an arbitrary number
 
26
      of times during a request).
27
27
"""
28
28
 
 
29
# XXX: The class names are a little confusing: the protocol will instantiate a
 
30
# SmartServerRequestHandler, whose dispatch_command method creates an instance
 
31
# of a SmartServerRequest subclass.
 
32
 
 
33
 
29
34
import tempfile
 
35
import threading
30
36
 
31
37
from bzrlib import (
32
38
    bzrdir,
33
39
    errors,
34
40
    registry,
35
41
    revision,
 
42
    trace,
36
43
    urlutils,
37
44
    )
38
 
from bzrlib.bundle.serializer import write_bundle
 
45
from bzrlib.lazy_import import lazy_import
 
46
lazy_import(globals(), """
 
47
from bzrlib.bundle import serializer
 
48
""")
 
49
 
 
50
 
 
51
jail_info = threading.local()
 
52
jail_info.transports = None
 
53
 
 
54
 
 
55
def _install_hook():
 
56
    bzrdir.BzrDir.hooks.install_named_hook(
 
57
        'pre_open', _pre_open_hook, 'checking server jail')
 
58
 
 
59
 
 
60
def _pre_open_hook(transport):
 
61
    allowed_transports = getattr(jail_info, 'transports', None)
 
62
    if allowed_transports is None:
 
63
        return
 
64
    abspath = transport.base
 
65
    for allowed_transport in allowed_transports:
 
66
        try:
 
67
            allowed_transport.relpath(abspath)
 
68
        except errors.PathNotChild:
 
69
            continue
 
70
        else:
 
71
            return
 
72
    raise errors.JailBreak(abspath)
 
73
 
 
74
 
 
75
_install_hook()
39
76
 
40
77
 
41
78
class SmartServerRequest(object):
42
79
    """Base class for request handlers.
43
 
    
 
80
 
44
81
    To define a new request, subclass this class and override the `do` method
45
82
    (and if appropriate, `do_body` as well).  Request implementors should take
46
83
    care to call `translate_client_path` and `transport_from_client_path` as
67
104
            if not root_client_path.endswith('/'):
68
105
                root_client_path += '/'
69
106
        self._root_client_path = root_client_path
 
107
        self._body_chunks = []
70
108
 
71
109
    def _check_enabled(self):
72
110
        """Raises DisabledMethod if this method is disabled."""
74
112
 
75
113
    def do(self, *args):
76
114
        """Mandatory extension point for SmartServerRequest subclasses.
77
 
        
 
115
 
78
116
        Subclasses must implement this.
79
 
        
 
117
 
80
118
        This should return a SmartServerResponse if this command expects to
81
119
        receive no body.
82
120
        """
97
135
        """Called if the client sends a body with the request.
98
136
 
99
137
        The do() method is still called, and must have returned None.
100
 
        
 
138
 
101
139
        Must return a SmartServerResponse.
102
140
        """
103
 
        raise NotImplementedError(self.do_body)
 
141
        if body_bytes != '':
 
142
            raise errors.SmartProtocolError('Request does not expect a body')
104
143
 
105
144
    def do_chunk(self, chunk_bytes):
106
145
        """Called with each body chunk if the request has a streamed body.
107
146
 
108
147
        The do() method is still called, and must have returned None.
109
148
        """
110
 
        raise NotImplementedError(self.do_chunk)
 
149
        self._body_chunks.append(chunk_bytes)
111
150
 
112
151
    def do_end(self):
113
152
        """Called when the end of the request has been received."""
114
 
        pass
115
 
    
 
153
        body_bytes = ''.join(self._body_chunks)
 
154
        self._body_chunks = None
 
155
        return self.do_body(body_bytes)
 
156
 
 
157
    def setup_jail(self):
 
158
        jail_info.transports = [self._backing_transport]
 
159
 
 
160
    def teardown_jail(self):
 
161
        jail_info.transports = None
 
162
 
116
163
    def translate_client_path(self, client_path):
117
164
        """Translate a path received from a network client into a local
118
165
        relpath.
129
176
            return client_path
130
177
        if not client_path.startswith('/'):
131
178
            client_path = '/' + client_path
 
179
        if client_path + '/' == self._root_client_path:
 
180
            return '.'
132
181
        if client_path.startswith(self._root_client_path):
133
182
            path = client_path[len(self._root_client_path):]
134
183
            relpath = urlutils.joinpath('/', path)
151
200
 
152
201
class SmartServerResponse(object):
153
202
    """A response to a client request.
154
 
    
 
203
 
155
204
    This base class should not be used. Instead use
156
205
    SuccessfulSmartServerResponse and FailedSmartServerResponse as appropriate.
157
206
    """
179
228
                other.body_stream is self.body_stream)
180
229
 
181
230
    def __repr__(self):
182
 
        status = {True: 'OK', False: 'ERR'}[self.is_successful()]
183
 
        return "<SmartServerResponse status=%s args=%r body=%r>" % (status,
 
231
        return "<%s args=%r body=%r>" % (self.__class__.__name__,
184
232
            self.args, self.body)
185
233
 
186
234
 
202
250
 
203
251
class SmartServerRequestHandler(object):
204
252
    """Protocol logic for smart server.
205
 
    
 
253
 
206
254
    This doesn't handle serialization at all, it just processes requests and
207
255
    creates responses.
208
256
    """
227
275
        self._backing_transport = backing_transport
228
276
        self._root_client_path = root_client_path
229
277
        self._commands = commands
230
 
        self._body_bytes = ''
231
278
        self.response = None
232
279
        self.finished_reading = False
233
280
        self._command = None
234
281
 
235
282
    def accept_body(self, bytes):
236
283
        """Accept body data."""
237
 
 
238
 
        # TODO: This should be overriden for each command that desired body data
239
 
        # to handle the right format of that data, i.e. plain bytes, a bundle,
240
 
        # etc.  The deserialisation into that format should be done in the
241
 
        # Protocol object.
242
 
 
243
 
        # default fallback is to accumulate bytes.
244
 
        self._body_bytes += bytes
245
 
        
 
284
        if self._command is None:
 
285
            # no active command object, so ignore the event.
 
286
            return
 
287
        self._run_handler_code(self._command.do_chunk, (bytes,), {})
 
288
 
246
289
    def end_of_body(self):
247
290
        """No more body data will be received."""
248
 
        self._run_handler_code(self._command.do_body, (self._body_bytes,), {})
 
291
        self._run_handler_code(self._command.do_end, (), {})
249
292
        # cannot read after this.
250
293
        self.finished_reading = True
251
294
 
278
321
        # XXX: most of this error conversion is VFS-related, and thus ought to
279
322
        # be in SmartServerVFSRequestHandler somewhere.
280
323
        try:
281
 
            return callable(*args, **kwargs)
282
 
        except errors.NoSuchFile, e:
283
 
            return FailedSmartServerResponse(('NoSuchFile', e.path))
284
 
        except errors.FileExists, e:
285
 
            return FailedSmartServerResponse(('FileExists', e.path))
286
 
        except errors.DirectoryNotEmpty, e:
287
 
            return FailedSmartServerResponse(('DirectoryNotEmpty', e.path))
288
 
        except errors.ShortReadvError, e:
289
 
            return FailedSmartServerResponse(('ShortReadvError',
290
 
                e.path, str(e.offset), str(e.length), str(e.actual)))
291
 
        except UnicodeError, e:
292
 
            # If it is a DecodeError, than most likely we are starting
293
 
            # with a plain string
294
 
            str_or_unicode = e.object
295
 
            if isinstance(str_or_unicode, unicode):
296
 
                # XXX: UTF-8 might have \x01 (our seperator byte) in it.  We
297
 
                # should escape it somehow.
298
 
                val = 'u:' + str_or_unicode.encode('utf-8')
299
 
            else:
300
 
                val = 's:' + str_or_unicode.encode('base64')
301
 
            # This handles UnicodeEncodeError or UnicodeDecodeError
302
 
            return FailedSmartServerResponse((e.__class__.__name__,
303
 
                    e.encoding, val, str(e.start), str(e.end), e.reason))
304
 
        except errors.TransportNotPossible, e:
305
 
            if e.msg == "readonly transport":
306
 
                return FailedSmartServerResponse(('ReadOnlyError', ))
307
 
            else:
308
 
                raise
 
324
            self._command.setup_jail()
 
325
            try:
 
326
                return callable(*args, **kwargs)
 
327
            finally:
 
328
                self._command.teardown_jail()
 
329
        except (KeyboardInterrupt, SystemExit):
 
330
            raise
 
331
        except Exception, err:
 
332
            err_struct = _translate_error(err)
 
333
            return FailedSmartServerResponse(err_struct)
309
334
 
310
335
    def headers_received(self, headers):
311
336
        # Just a no-op at the moment.
321
346
        self._command = command(self._backing_transport)
322
347
        self._run_handler_code(self._command.execute, args, {})
323
348
 
324
 
    def prefixed_body_received(self, body_bytes):
325
 
        """No more body data will be received."""
326
 
        self._run_handler_code(self._command.do_body, (body_bytes,), {})
327
 
        # cannot read after this.
328
 
        self.finished_reading = True
329
 
 
330
 
    def body_chunk_received(self, chunk_bytes):
331
 
        self._run_handler_code(self._command.do_chunk, (chunk_bytes,), {})
332
 
 
333
349
    def end_received(self):
 
350
        if self._command is None:
 
351
            # no active command object, so ignore the event.
 
352
            return
334
353
        self._run_handler_code(self._command.do_end, (), {})
335
354
 
 
355
    def post_body_error_received(self, error_args):
 
356
        # Just a no-op at the moment.
 
357
        pass
 
358
 
 
359
 
 
360
def _translate_error(err):
 
361
    if isinstance(err, errors.NoSuchFile):
 
362
        return ('NoSuchFile', err.path)
 
363
    elif isinstance(err, errors.FileExists):
 
364
        return ('FileExists', err.path)
 
365
    elif isinstance(err, errors.DirectoryNotEmpty):
 
366
        return ('DirectoryNotEmpty', err.path)
 
367
    elif isinstance(err, errors.IncompatibleRepositories):
 
368
        return ('IncompatibleRepositories', str(err.source), str(err.target),
 
369
            str(err.details))
 
370
    elif isinstance(err, errors.ShortReadvError):
 
371
        return ('ShortReadvError', err.path, str(err.offset), str(err.length),
 
372
                str(err.actual))
 
373
    elif isinstance(err, errors.UnstackableRepositoryFormat):
 
374
        return (('UnstackableRepositoryFormat', str(err.format), err.url))
 
375
    elif isinstance(err, errors.UnstackableBranchFormat):
 
376
        return ('UnstackableBranchFormat', str(err.format), err.url)
 
377
    elif isinstance(err, errors.NotStacked):
 
378
        return ('NotStacked',)
 
379
    elif isinstance(err, UnicodeError):
 
380
        # If it is a DecodeError, than most likely we are starting
 
381
        # with a plain string
 
382
        str_or_unicode = err.object
 
383
        if isinstance(str_or_unicode, unicode):
 
384
            # XXX: UTF-8 might have \x01 (our protocol v1 and v2 seperator
 
385
            # byte) in it, so this encoding could cause broken responses.
 
386
            # Newer clients use protocol v3, so will be fine.
 
387
            val = 'u:' + str_or_unicode.encode('utf-8')
 
388
        else:
 
389
            val = 's:' + str_or_unicode.encode('base64')
 
390
        # This handles UnicodeEncodeError or UnicodeDecodeError
 
391
        return (err.__class__.__name__, err.encoding, val, str(err.start),
 
392
                str(err.end), err.reason)
 
393
    elif isinstance(err, errors.TransportNotPossible):
 
394
        if err.msg == "readonly transport":
 
395
            return ('ReadOnlyError', )
 
396
    elif isinstance(err, errors.ReadError):
 
397
        # cannot read the file
 
398
        return ('ReadError', err.path)
 
399
    elif isinstance(err, errors.PermissionDenied):
 
400
        return ('PermissionDenied', err.path, err.extra)
 
401
    elif isinstance(err, errors.TokenMismatch):
 
402
        return ('TokenMismatch', err.given_token, err.lock_token)
 
403
    elif isinstance(err, errors.LockContention):
 
404
        return ('LockContention',)
 
405
    # Unserialisable error.  Log it, and return a generic error
 
406
    trace.log_exception_quietly()
 
407
    return ('error', str(err))
 
408
 
336
409
 
337
410
class HelloRequest(SmartServerRequest):
338
411
    """Answer a version request with the highest protocol version this server
353
426
        repo = control.open_repository()
354
427
        tmpf = tempfile.TemporaryFile()
355
428
        base_revision = revision.NULL_REVISION
356
 
        write_bundle(repo, revision_id, base_revision, tmpf)
 
429
        serializer.write_bundle(repo, revision_id, base_revision, tmpf)
357
430
        tmpf.seek(0)
358
431
        return SuccessfulSmartServerResponse((), tmpf.read())
359
432
 
373
446
request_handlers.register_lazy(
374
447
    'append', 'bzrlib.smart.vfs', 'AppendRequest')
375
448
request_handlers.register_lazy(
376
 
    'Branch.get_config_file', 'bzrlib.smart.branch', 'SmartServerBranchGetConfigFile')
 
449
    'Branch.get_config_file', 'bzrlib.smart.branch',
 
450
    'SmartServerBranchGetConfigFile')
 
451
request_handlers.register_lazy(
 
452
    'Branch.get_parent', 'bzrlib.smart.branch', 'SmartServerBranchGetParent')
 
453
request_handlers.register_lazy(
 
454
    'Branch.get_tags_bytes', 'bzrlib.smart.branch',
 
455
    'SmartServerBranchGetTagsBytes')
 
456
request_handlers.register_lazy(
 
457
    'Branch.set_tags_bytes', 'bzrlib.smart.branch',
 
458
    'SmartServerBranchSetTagsBytes')
 
459
request_handlers.register_lazy(
 
460
    'Branch.get_stacked_on_url', 'bzrlib.smart.branch', 'SmartServerBranchRequestGetStackedOnURL')
377
461
request_handlers.register_lazy(
378
462
    'Branch.last_revision_info', 'bzrlib.smart.branch', 'SmartServerBranchRequestLastRevisionInfo')
379
463
request_handlers.register_lazy(
380
464
    'Branch.lock_write', 'bzrlib.smart.branch', 'SmartServerBranchRequestLockWrite')
381
 
request_handlers.register_lazy(
382
 
    'Branch.revision_history', 'bzrlib.smart.branch', 'SmartServerRequestRevisionHistory')
383
 
request_handlers.register_lazy(
384
 
    'Branch.set_last_revision', 'bzrlib.smart.branch', 'SmartServerBranchRequestSetLastRevision')
 
465
request_handlers.register_lazy( 'Branch.revision_history',
 
466
    'bzrlib.smart.branch', 'SmartServerRequestRevisionHistory')
 
467
request_handlers.register_lazy( 'Branch.set_config_option',
 
468
    'bzrlib.smart.branch', 'SmartServerBranchRequestSetConfigOption')
 
469
request_handlers.register_lazy( 'Branch.set_last_revision',
 
470
    'bzrlib.smart.branch', 'SmartServerBranchRequestSetLastRevision')
385
471
request_handlers.register_lazy(
386
472
    'Branch.set_last_revision_info', 'bzrlib.smart.branch',
387
473
    'SmartServerBranchRequestSetLastRevisionInfo')
389
475
    'Branch.set_last_revision_ex', 'bzrlib.smart.branch',
390
476
    'SmartServerBranchRequestSetLastRevisionEx')
391
477
request_handlers.register_lazy(
 
478
    'Branch.set_parent_location', 'bzrlib.smart.branch',
 
479
    'SmartServerBranchRequestSetParentLocation')
 
480
request_handlers.register_lazy(
392
481
    'Branch.unlock', 'bzrlib.smart.branch', 'SmartServerBranchRequestUnlock')
393
482
request_handlers.register_lazy(
394
 
    'BzrDir.find_repository', 'bzrlib.smart.bzrdir', 'SmartServerRequestFindRepositoryV1')
395
 
request_handlers.register_lazy(
396
 
    'BzrDir.find_repositoryV2', 'bzrlib.smart.bzrdir', 'SmartServerRequestFindRepositoryV2')
397
 
request_handlers.register_lazy(
398
 
    'BzrDirFormat.initialize', 'bzrlib.smart.bzrdir', 'SmartServerRequestInitializeBzrDir')
399
 
request_handlers.register_lazy(
400
 
    'BzrDir.open_branch', 'bzrlib.smart.bzrdir', 'SmartServerRequestOpenBranch')
 
483
    'BzrDir.cloning_metadir', 'bzrlib.smart.bzrdir',
 
484
    'SmartServerBzrDirRequestCloningMetaDir')
 
485
request_handlers.register_lazy(
 
486
    'BzrDir.create_branch', 'bzrlib.smart.bzrdir',
 
487
    'SmartServerRequestCreateBranch')
 
488
request_handlers.register_lazy(
 
489
    'BzrDir.create_repository', 'bzrlib.smart.bzrdir',
 
490
    'SmartServerRequestCreateRepository')
 
491
request_handlers.register_lazy(
 
492
    'BzrDir.find_repository', 'bzrlib.smart.bzrdir',
 
493
    'SmartServerRequestFindRepositoryV1')
 
494
request_handlers.register_lazy(
 
495
    'BzrDir.find_repositoryV2', 'bzrlib.smart.bzrdir',
 
496
    'SmartServerRequestFindRepositoryV2')
 
497
request_handlers.register_lazy(
 
498
    'BzrDir.find_repositoryV3', 'bzrlib.smart.bzrdir',
 
499
    'SmartServerRequestFindRepositoryV3')
 
500
request_handlers.register_lazy(
 
501
    'BzrDir.get_config_file', 'bzrlib.smart.bzrdir',
 
502
    'SmartServerBzrDirRequestConfigFile')
 
503
request_handlers.register_lazy(
 
504
    'BzrDirFormat.initialize', 'bzrlib.smart.bzrdir',
 
505
    'SmartServerRequestInitializeBzrDir')
 
506
request_handlers.register_lazy(
 
507
    'BzrDirFormat.initialize_ex_1.16', 'bzrlib.smart.bzrdir',
 
508
    'SmartServerRequestBzrDirInitializeEx')
 
509
request_handlers.register_lazy(
 
510
    'BzrDir.open', 'bzrlib.smart.bzrdir', 'SmartServerRequestOpenBzrDir')
 
511
request_handlers.register_lazy(
 
512
    'BzrDir.open_branch', 'bzrlib.smart.bzrdir',
 
513
    'SmartServerRequestOpenBranch')
 
514
request_handlers.register_lazy(
 
515
    'BzrDir.open_branchV2', 'bzrlib.smart.bzrdir',
 
516
    'SmartServerRequestOpenBranchV2')
401
517
request_handlers.register_lazy(
402
518
    'delete', 'bzrlib.smart.vfs', 'DeleteRequest')
403
519
request_handlers.register_lazy(
424
540
    'readv', 'bzrlib.smart.vfs', 'ReadvRequest')
425
541
request_handlers.register_lazy(
426
542
    'rename', 'bzrlib.smart.vfs', 'RenameRequest')
 
543
request_handlers.register_lazy(
 
544
    'PackRepository.autopack', 'bzrlib.smart.packrepository',
 
545
    'SmartServerPackRepositoryAutopack')
427
546
request_handlers.register_lazy('Repository.gather_stats',
428
547
                               'bzrlib.smart.repository',
429
548
                               'SmartServerRepositoryGatherStats')
435
554
request_handlers.register_lazy(
436
555
    'Repository.has_revision', 'bzrlib.smart.repository', 'SmartServerRequestHasRevision')
437
556
request_handlers.register_lazy(
 
557
    'Repository.insert_stream', 'bzrlib.smart.repository', 'SmartServerRepositoryInsertStream')
 
558
request_handlers.register_lazy(
 
559
    'Repository.insert_stream_1.19', 'bzrlib.smart.repository', 'SmartServerRepositoryInsertStream_1_19')
 
560
request_handlers.register_lazy(
 
561
    'Repository.insert_stream_locked', 'bzrlib.smart.repository', 'SmartServerRepositoryInsertStreamLocked')
 
562
request_handlers.register_lazy(
438
563
    'Repository.is_shared', 'bzrlib.smart.repository', 'SmartServerRepositoryIsShared')
439
564
request_handlers.register_lazy(
440
565
    'Repository.lock_write', 'bzrlib.smart.repository', 'SmartServerRepositoryLockWrite')
441
566
request_handlers.register_lazy(
 
567
    'Repository.set_make_working_trees', 'bzrlib.smart.repository',
 
568
    'SmartServerRepositorySetMakeWorkingTrees')
 
569
request_handlers.register_lazy(
442
570
    'Repository.unlock', 'bzrlib.smart.repository', 'SmartServerRepositoryUnlock')
443
571
request_handlers.register_lazy(
 
572
    'Repository.get_rev_id_for_revno', 'bzrlib.smart.repository',
 
573
    'SmartServerRepositoryGetRevIdForRevno')
 
574
request_handlers.register_lazy(
 
575
    'Repository.get_stream', 'bzrlib.smart.repository',
 
576
    'SmartServerRepositoryGetStream')
 
577
request_handlers.register_lazy(
 
578
    'Repository.get_stream_1.19', 'bzrlib.smart.repository',
 
579
    'SmartServerRepositoryGetStream_1_19')
 
580
request_handlers.register_lazy(
444
581
    'Repository.tarball', 'bzrlib.smart.repository',
445
582
    'SmartServerRepositoryTarball')
446
583
request_handlers.register_lazy(
449
586
    'stat', 'bzrlib.smart.vfs', 'StatRequest')
450
587
request_handlers.register_lazy(
451
588
    'Transport.is_readonly', 'bzrlib.smart.request', 'SmartServerIsReadonly')
452
 
request_handlers.register_lazy(
453
 
    'BzrDir.open', 'bzrlib.smart.bzrdir', 'SmartServerRequestOpenBzrDir')