Rev 4148: (andrew) Add RPC that can insert a stream into older (i.e. lockable) in file:///home/pqm/archives/thelove/bzr/%2Btrunk/

Canonical.com Patch Queue Manager pqm at pqm.ubuntu.com
Mon Mar 16 06:51:40 GMT 2009


At file:///home/pqm/archives/thelove/bzr/%2Btrunk/

------------------------------------------------------------
revno: 4148
revision-id: pqm at pqm.ubuntu.com-20090316065136-1g3udxbvvlgtsbup
parent: pqm at pqm.ubuntu.com-20090316041621-taek91nogxt42bfy
parent: andrew.bennetts at canonical.com-20090316055842-6jmbqwy3q4apljtn
committer: Canonical.com Patch Queue Manager <pqm at pqm.ubuntu.com>
branch nick: +trunk
timestamp: Mon 2009-03-16 06:51:36 +0000
message:
  (andrew) Add RPC that can insert a stream into older (i.e. lockable)
  	repository formats.
modified:
  NEWS                           NEWS-20050323055033-4e00b5db738777ff
  bzrlib/remote.py               remote.py-20060720103555-yeeg2x51vn0rbtdp-1
  bzrlib/smart/repository.py     repository.py-20061128022038-vr5wy5bubyb8xttk-1
  bzrlib/smart/request.py        request.py-20061108095550-gunadhxmzkdjfeek-1
  bzrlib/tests/test_remote.py    test_remote.py-20060720103555-yeeg2x51vn0rbtdp-2
  bzrlib/tests/test_smart.py     test_smart.py-20061122024551-ol0l0o0oofsu9b3t-2
  bzrlib/tests/test_smart_request.py test_smart_request.p-20090211070731-o38wayv3asm25d6a-1
    ------------------------------------------------------------
    revno: 4144.3.5
    revision-id: andrew.bennetts at canonical.com-20090316055842-6jmbqwy3q4apljtn
    parent: andrew.bennetts at canonical.com-20090316055832-fn87u872p0kctjrt
    parent: pqm at pqm.ubuntu.com-20090316041621-taek91nogxt42bfy
    committer: Andrew Bennetts <andrew.bennetts at canonical.com>
    branch nick: simplify-interrepo-stack
    timestamp: Mon 2009-03-16 16:58:42 +1100
    message:
      Merge from bzr.dev
    modified:
      NEWS                           NEWS-20050323055033-4e00b5db738777ff
      bzrlib/builtins.py             builtins.py-20050830033751-fc01482b9ca23183
      bzrlib/tests/__init__.py       selftest.py-20050531073622-8d0e3c8845c97a64
      bzrlib/tests/blackbox/test_merge.py test_merge.py-20060323225809-9bc0459c19917f41
      bzrlib/tests/test_selftest.py  test_selftest.py-20051202044319-c110a115d8c0456a
    ------------------------------------------------------------
    revno: 4144.3.4
    revision-id: andrew.bennetts at canonical.com-20090316055832-fn87u872p0kctjrt
    parent: andrew.bennetts at canonical.com-20090316055542-8dpb2i3xvmairepn
    committer: Andrew Bennetts <andrew.bennetts at canonical.com>
    branch nick: simplify-interrepo-stack
    timestamp: Mon 2009-03-16 16:58:32 +1100
    message:
      Add NEWS entry.
    modified:
      NEWS                           NEWS-20050323055033-4e00b5db738777ff
    ------------------------------------------------------------
    revno: 4144.3.3
    revision-id: andrew.bennetts at canonical.com-20090316055542-8dpb2i3xvmairepn
    parent: andrew.bennetts at canonical.com-20090316054753-0mfo07r21tsjl5p0
    committer: Andrew Bennetts <andrew.bennetts at canonical.com>
    branch nick: simplify-interrepo-stack
    timestamp: Mon 2009-03-16 16:55:42 +1100
    message:
      Tweaks based on review from Robert.
    modified:
      bzrlib/smart/repository.py     repository.py-20061128022038-vr5wy5bubyb8xttk-1
      bzrlib/tests/test_remote.py    test_remote.py-20060720103555-yeeg2x51vn0rbtdp-2
    ------------------------------------------------------------
    revno: 4144.3.2
    revision-id: andrew.bennetts at canonical.com-20090316054753-0mfo07r21tsjl5p0
    parent: andrew.bennetts at canonical.com-20090316044809-0bsw43pof8fq7byl
    committer: Andrew Bennetts <andrew.bennetts at canonical.com>
    branch nick: simplify-interrepo-stack
    timestamp: Mon 2009-03-16 16:47:53 +1100
    message:
      Use Repository.insert_stream_locked if there is a lock_token for the remote repo.
    modified:
      bzrlib/remote.py               remote.py-20060720103555-yeeg2x51vn0rbtdp-1
      bzrlib/tests/test_remote.py    test_remote.py-20060720103555-yeeg2x51vn0rbtdp-2
    ------------------------------------------------------------
    revno: 4144.3.1
    revision-id: andrew.bennetts at canonical.com-20090316044809-0bsw43pof8fq7byl
    parent: andrew.bennetts at canonical.com-20090316022525-49vw2o0on4foz3u0
    committer: Andrew Bennetts <andrew.bennetts at canonical.com>
    branch nick: simplify-interrepo-stack
    timestamp: Mon 2009-03-16 15:48:09 +1100
    message:
      Add Repository.insert_stream_locked server-side implementation, plus tests for server-side _translate_error.
    modified:
      bzrlib/smart/repository.py     repository.py-20061128022038-vr5wy5bubyb8xttk-1
      bzrlib/smart/request.py        request.py-20061108095550-gunadhxmzkdjfeek-1
      bzrlib/tests/test_smart.py     test_smart.py-20061122024551-ol0l0o0oofsu9b3t-2
      bzrlib/tests/test_smart_request.py test_smart_request.p-20090211070731-o38wayv3asm25d6a-1
=== modified file 'NEWS'
--- a/NEWS	2009-03-16 03:35:34 +0000
+++ b/NEWS	2009-03-16 05:58:42 +0000
@@ -32,6 +32,10 @@
     * Progress bars now show the rate of network activity for
       ``bzr+ssh://`` and ``bzr://`` connections.  (Andrew Bennetts)
 
+    * Streaming push can be done to older repository formats.  This is
+      implemented using a new ``Repository.insert_stream_locked`` RPC.
+      (Andrew Bennetts, Robert Collins)
+
     * Tildes are no longer escaped. No more %7Euser/project/branch!
       (Jonathan Lange)
 

=== modified file 'bzrlib/remote.py'
--- a/bzrlib/remote.py	2009-03-12 08:50:04 +0000
+++ b/bzrlib/remote.py	2009-03-16 05:47:53 +0000
@@ -1522,13 +1522,21 @@
         return result
 
     def insert_stream(self, stream, src_format, resume_tokens):
-        repo = self.target_repo
-        client = repo._client
+        target = self.target_repo
+        if target._lock_token:
+            verb = 'Repository.insert_stream_locked'
+            extra_args = (target._lock_token or '',)
+            required_version = (1, 14)
+        else:
+            verb = 'Repository.insert_stream'
+            extra_args = ()
+            required_version = (1, 13)
+        client = target._client
         medium = client._medium
-        if medium._is_remote_before((1, 13)):
+        if medium._is_remote_before(required_version):
             # No possible way this can work.
             return self._insert_real(stream, src_format, resume_tokens)
-        path = repo.bzrdir._path_for_remote_call(client)
+        path = target.bzrdir._path_for_remote_call(client)
         if not resume_tokens:
             # XXX: Ugly but important for correctness, *will* be fixed during
             # 1.13 cycle. Pushing a stream that is interrupted results in a
@@ -1541,15 +1549,15 @@
             byte_stream = smart_repo._stream_to_byte_stream([], src_format)
             try:
                 response = client.call_with_body_stream(
-                    ('Repository.insert_stream', path, ''), byte_stream)
+                    (verb, path, '') + extra_args, byte_stream)
             except errors.UnknownSmartMethod:
-                medium._remember_remote_is_before((1,13))
+                medium._remember_remote_is_before(required_version)
                 return self._insert_real(stream, src_format, resume_tokens)
         byte_stream = smart_repo._stream_to_byte_stream(
             stream, src_format)
         resume_tokens = ' '.join(resume_tokens)
         response = client.call_with_body_stream(
-            ('Repository.insert_stream', path, resume_tokens), byte_stream)
+            (verb, path, resume_tokens) + extra_args, byte_stream)
         if response[0][0] not in ('ok', 'missing-basis'):
             raise errors.UnexpectedSmartServerResponse(response)
         if response[0][0] == 'missing-basis':

=== modified file 'bzrlib/smart/repository.py'
--- a/bzrlib/smart/repository.py	2009-03-06 08:13:23 +0000
+++ b/bzrlib/smart/repository.py	2009-03-16 05:55:42 +0000
@@ -524,17 +524,22 @@
             tarball.close()
 
 
-class SmartServerRepositoryInsertStream(SmartServerRepositoryRequest):
+class SmartServerRepositoryInsertStreamLocked(SmartServerRepositoryRequest):
     """Insert a record stream from a RemoteSink into a repository.
 
     This gets bytes pushed to it by the network infrastructure and turns that
     into a bytes iterator using a thread. That is then processed by
     _byte_stream_to_stream.
+
+    New in 1.14.
     """
 
-    def do_repository_request(self, repository, resume_tokens):
+    def do_repository_request(self, repository, resume_tokens, lock_token):
         """StreamSink.insert_stream for a remote repository."""
-        repository.lock_write()
+        repository.lock_write(token=lock_token)
+        self.do_insert_stream_request(repository, resume_tokens)
+
+    def do_insert_stream_request(self, repository, resume_tokens):
         tokens = [token for token in resume_tokens.split(' ') if token]
         self.tokens = tokens
         self.repository = repository
@@ -582,3 +587,21 @@
         else:
             self.repository.unlock()
             return SuccessfulSmartServerResponse(('ok', ))
+
+
+class SmartServerRepositoryInsertStream(SmartServerRepositoryInsertStreamLocked):
+    """Insert a record stream from a RemoteSink into an unlocked repository.
+
+    This is the same as SmartServerRepositoryInsertStreamLocked, except it
+    takes no lock_tokens; i.e. it works with an unlocked (or lock-free, e.g.
+    like pack format) repository.
+
+    New in 1.13.
+    """
+
+    def do_repository_request(self, repository, resume_tokens):
+        """StreamSink.insert_stream for a remote repository."""
+        repository.lock_write()
+        self.do_insert_stream_request(repository, resume_tokens)
+
+

=== modified file 'bzrlib/smart/request.py'
--- a/bzrlib/smart/request.py	2009-03-06 03:55:27 +0000
+++ b/bzrlib/smart/request.py	2009-03-16 04:48:09 +0000
@@ -344,6 +344,10 @@
         return ('ReadError', err.path)
     elif isinstance(err, errors.PermissionDenied):
         return ('PermissionDenied', err.path, err.extra)
+    elif isinstance(err, errors.TokenMismatch):
+        return ('TokenMismatch', err.given_token, err.lock_token)
+    elif isinstance(err, errors.LockContention):
+        return ('LockContention', err.lock, err.msg)
     # Unserialisable error.  Log it, and return a generic error
     trace.log_exception_quietly()
     return ('error', str(err))
@@ -482,6 +486,8 @@
 request_handlers.register_lazy(
     'Repository.insert_stream', 'bzrlib.smart.repository', 'SmartServerRepositoryInsertStream')
 request_handlers.register_lazy(
+    'Repository.insert_stream_locked', 'bzrlib.smart.repository', 'SmartServerRepositoryInsertStreamLocked')
+request_handlers.register_lazy(
     'Repository.is_shared', 'bzrlib.smart.repository', 'SmartServerRepositoryIsShared')
 request_handlers.register_lazy(
     'Repository.lock_write', 'bzrlib.smart.repository', 'SmartServerRepositoryLockWrite')

=== modified file 'bzrlib/tests/test_remote.py'
--- a/bzrlib/tests/test_remote.py	2009-03-13 02:13:08 +0000
+++ b/bzrlib/tests/test_remote.py	2009-03-16 05:55:42 +0000
@@ -270,7 +270,11 @@
         stream = list(stream)
         self._check_call(args[0], args[1:])
         self._calls.append(('call_with_body_stream', args[0], args[1:], stream))
-        return self._get_next_response()[1]
+        result = self._get_next_response()
+        # The second value returned from call_with_body_stream is supposed to
+        # be a response_handler object, but so far no tests depend on that.
+        response_handler = None 
+        return result[1], response_handler
 
 
 class FakeMedium(medium.SmartClientMedium):
@@ -1825,6 +1829,65 @@
         self.assertEqual([], client._calls)
 
 
+class TestRepositoryInsertStream(TestRemoteRepository):
+
+    def test_unlocked_repo(self):
+        transport_path = 'quack'
+        repo, client = self.setup_fake_client_and_repository(transport_path)
+        client.add_expected_call(
+            'Repository.insert_stream', ('quack/', ''),
+            'success', ('ok',))
+        client.add_expected_call(
+            'Repository.insert_stream', ('quack/', ''),
+            'success', ('ok',))
+        sink = repo._get_sink()
+        fmt = repository.RepositoryFormat.get_default_format()
+        resume_tokens, missing_keys = sink.insert_stream([], fmt, [])
+        self.assertEqual([], resume_tokens)
+        self.assertEqual(set(), missing_keys)
+        client.finished_test()
+
+    def test_locked_repo_with_no_lock_token(self):
+        transport_path = 'quack'
+        repo, client = self.setup_fake_client_and_repository(transport_path)
+        client.add_expected_call(
+            'Repository.lock_write', ('quack/', ''),
+            'success', ('ok', ''))
+        client.add_expected_call(
+            'Repository.insert_stream', ('quack/', ''),
+            'success', ('ok',))
+        client.add_expected_call(
+            'Repository.insert_stream', ('quack/', ''),
+            'success', ('ok',))
+        repo.lock_write()
+        sink = repo._get_sink()
+        fmt = repository.RepositoryFormat.get_default_format()
+        resume_tokens, missing_keys = sink.insert_stream([], fmt, [])
+        self.assertEqual([], resume_tokens)
+        self.assertEqual(set(), missing_keys)
+        client.finished_test()
+
+    def test_locked_repo_with_lock_token(self):
+        transport_path = 'quack'
+        repo, client = self.setup_fake_client_and_repository(transport_path)
+        client.add_expected_call(
+            'Repository.lock_write', ('quack/', ''),
+            'success', ('ok', 'a token'))
+        client.add_expected_call(
+            'Repository.insert_stream_locked', ('quack/', '', 'a token'),
+            'success', ('ok',))
+        client.add_expected_call(
+            'Repository.insert_stream_locked', ('quack/', '', 'a token'),
+            'success', ('ok',))
+        repo.lock_write()
+        sink = repo._get_sink()
+        fmt = repository.RepositoryFormat.get_default_format()
+        resume_tokens, missing_keys = sink.insert_stream([], fmt, [])
+        self.assertEqual([], resume_tokens)
+        self.assertEqual(set(), missing_keys)
+        client.finished_test()
+
+
 class TestRepositoryTarball(TestRemoteRepository):
 
     # This is a canned tarball reponse we can validate against

=== modified file 'bzrlib/tests/test_smart.py'
--- a/bzrlib/tests/test_smart.py	2009-03-09 08:45:56 +0000
+++ b/bzrlib/tests/test_smart.py	2009-03-16 04:48:09 +0000
@@ -1114,9 +1114,6 @@
 
 class TestSmartServerRepositoryLockWrite(tests.TestCaseWithMemoryTransport):
 
-    def setUp(self):
-        tests.TestCaseWithMemoryTransport.setUp(self)
-
     def test_lock_write_on_unlocked_repo(self):
         backing = self.get_transport()
         request = smart.repository.SmartServerRepositoryLockWrite(backing)
@@ -1149,6 +1146,54 @@
         self.assertEqual('LockFailed', response.args[0])
 
 
+class TestInsertStreamBase(tests.TestCaseWithMemoryTransport):
+
+    def make_empty_byte_stream(self, repo):
+        byte_stream = smart.repository._stream_to_byte_stream([], repo._format)
+        return ''.join(byte_stream)
+
+
+class TestSmartServerRepositoryInsertStream(TestInsertStreamBase):
+
+    def test_insert_stream_empty(self):
+        backing = self.get_transport()
+        request = smart.repository.SmartServerRepositoryInsertStream(backing)
+        repository = self.make_repository('.')
+        response = request.execute('', '')
+        self.assertEqual(None, response)
+        response = request.do_chunk(self.make_empty_byte_stream(repository))
+        self.assertEqual(None, response)
+        response = request.do_end()
+        self.assertEqual(SmartServerResponse(('ok', )), response)
+        
+
+class TestSmartServerRepositoryInsertStreamLocked(TestInsertStreamBase):
+
+    def test_insert_stream_empty(self):
+        backing = self.get_transport()
+        request = smart.repository.SmartServerRepositoryInsertStreamLocked(
+            backing)
+        repository = self.make_repository('.', format='knit')
+        lock_token = repository.lock_write()
+        response = request.execute('', '', lock_token)
+        self.assertEqual(None, response)
+        response = request.do_chunk(self.make_empty_byte_stream(repository))
+        self.assertEqual(None, response)
+        response = request.do_end()
+        self.assertEqual(SmartServerResponse(('ok', )), response)
+        repository.unlock()
+
+    def test_insert_stream_with_wrong_lock_token(self):
+        backing = self.get_transport()
+        request = smart.repository.SmartServerRepositoryInsertStreamLocked(
+            backing)
+        repository = self.make_repository('.', format='knit')
+        lock_token = repository.lock_write()
+        self.assertRaises(
+            errors.TokenMismatch, request.execute, '', '', 'wrong-token')
+        repository.unlock()
+
+
 class TestSmartServerRepositoryUnlock(tests.TestCaseWithMemoryTransport):
 
     def setUp(self):
@@ -1339,18 +1384,24 @@
                 'Repository.get_revision_graph'),
             smart.repository.SmartServerRepositoryGetRevisionGraph)
         self.assertEqual(
+            smart.request.request_handlers.get('Repository.get_stream'),
+            smart.repository.SmartServerRepositoryGetStream)
+        self.assertEqual(
             smart.request.request_handlers.get('Repository.has_revision'),
             smart.repository.SmartServerRequestHasRevision)
         self.assertEqual(
+            smart.request.request_handlers.get('Repository.insert_stream'),
+            smart.repository.SmartServerRepositoryInsertStream)
+        self.assertEqual(
+            smart.request.request_handlers.get('Repository.insert_stream_locked'),
+            smart.repository.SmartServerRepositoryInsertStreamLocked)
+        self.assertEqual(
             smart.request.request_handlers.get('Repository.is_shared'),
             smart.repository.SmartServerRepositoryIsShared)
         self.assertEqual(
             smart.request.request_handlers.get('Repository.lock_write'),
             smart.repository.SmartServerRepositoryLockWrite)
         self.assertEqual(
-            smart.request.request_handlers.get('Repository.get_stream'),
-            smart.repository.SmartServerRepositoryGetStream)
-        self.assertEqual(
             smart.request.request_handlers.get('Repository.tarball'),
             smart.repository.SmartServerRepositoryTarball)
         self.assertEqual(

=== modified file 'bzrlib/tests/test_smart_request.py'
--- a/bzrlib/tests/test_smart_request.py	2009-03-02 05:38:55 +0000
+++ b/bzrlib/tests/test_smart_request.py	2009-03-16 04:48:09 +0000
@@ -28,6 +28,39 @@
         return request.SuccessfulSmartServerResponse(('ok',))
 
 
+class DoErrorRequest(request.SmartServerRequest):
+    """A request that raises an error from self.do()."""
+    
+    def do(self):
+        raise errors.NoSuchFile('xyzzy')
+
+
+class ChunkErrorRequest(request.SmartServerRequest):
+    """A request that raises an error from self.do_chunk()."""
+    
+    def do(self):
+        """No-op."""
+        pass
+
+    def do_chunk(self, bytes):
+        raise errors.NoSuchFile('xyzzy')
+
+
+class EndErrorRequest(request.SmartServerRequest):
+    """A request that raises an error from self.do_end()."""
+    
+    def do(self):
+        """No-op."""
+        pass
+
+    def do_chunk(self, bytes):
+        """No-op."""
+        pass
+        
+    def do_end(self):
+        raise errors.NoSuchFile('xyzzy')
+
+
 class TestSmartRequest(TestCase):
 
     def test_request_class_without_do_body(self):
@@ -43,3 +76,61 @@
         handler.end_received()
         # Request done, no exception was raised.
 
+
+class TestSmartRequestHandlerErrorTranslation(TestCase):
+    """Tests that SmartServerRequestHandler will translate exceptions raised by
+    a SmartServerRequest into FailedSmartServerResponses.
+    """
+
+    def assertNoResponse(self, handler):
+        self.assertEqual(None, handler.response)
+
+    def assertResponseIsTranslatedError(self, handler):
+        expected_translation = ('NoSuchFile', 'xyzzy')
+        self.assertEqual(
+            request.FailedSmartServerResponse(expected_translation),
+            handler.response)
+
+    def test_error_translation_from_args_received(self):
+        handler = request.SmartServerRequestHandler(
+            None, {'foo': DoErrorRequest}, '/')
+        handler.args_received(('foo',))
+        self.assertResponseIsTranslatedError(handler)
+
+    def test_error_translation_from_chunk_received(self):
+        handler = request.SmartServerRequestHandler(
+            None, {'foo': ChunkErrorRequest}, '/')
+        handler.args_received(('foo',))
+        self.assertNoResponse(handler)
+        handler.accept_body('bytes')
+        self.assertResponseIsTranslatedError(handler)
+
+    def test_error_translation_from_end_received(self):
+        handler = request.SmartServerRequestHandler(
+            None, {'foo': EndErrorRequest}, '/')
+        handler.args_received(('foo',))
+        self.assertNoResponse(handler)
+        handler.end_received()
+        self.assertResponseIsTranslatedError(handler)
+
+
+class TestRequestHanderErrorTranslation(TestCase):
+    """Tests for bzrlib.smart.request._translate_error."""
+
+    def assertTranslationEqual(self, expected_tuple, error):
+        self.assertEqual(expected_tuple, request._translate_error(error))
+
+    def test_NoSuchFile(self):
+        self.assertTranslationEqual(
+            ('NoSuchFile', 'path'), errors.NoSuchFile('path'))
+
+    def test_LockContention(self):
+        self.assertTranslationEqual(
+            ('LockContention', 'lock', 'msg'),
+            errors.LockContention('lock', 'msg'))
+
+    def test_TokenMismatch(self):
+        self.assertTranslationEqual(
+            ('TokenMismatch', 'some-token', 'actual-token'),
+            errors.TokenMismatch('some-token', 'actual-token'))
+




More information about the bazaar-commits mailing list