Rev 2592: (robertc) New method external_url on Transport for obtaining the url to hand to external processes. Include the external_url for the backing_transport of SmartServer in the server_started and stopped hooks. (Robert Collins) in file:///home/pqm/archives/thelove/bzr/%2Btrunk/

Canonical.com Patch Queue Manager pqm at pqm.ubuntu.com
Thu Jul 5 23:42:49 BST 2007


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

------------------------------------------------------------
revno: 2592
revision-id: pqm at pqm.ubuntu.com-20070705224207-7pslqt12ofh4vnzx
parent: pqm at pqm.ubuntu.com-20070705213250-ae4p76jrqrmo3dko
parent: robertc at robertcollins.net-20070705205402-zlb38672nxlb0jsl
committer: Canonical.com Patch Queue Manager<pqm at pqm.ubuntu.com>
branch nick: +trunk
timestamp: Thu 2007-07-05 23:42:07 +0100
message:
  (robertc) New method external_url on Transport for obtaining the url to hand to external processes. Include the external_url for the backing_transport of SmartServer in the server_started and stopped hooks. (Robert Collins)
modified:
  NEWS                           NEWS-20050323055033-4e00b5db738777ff
  bzrlib/errors.py               errors.py-20050309040759-20512168c4e14fbd
  bzrlib/smart/server.py         server.py-20061110062051-chzu10y32vx8gvur-1
  bzrlib/tests/test_errors.py    test_errors.py-20060210110251-41aba2deddf936a8
  bzrlib/tests/test_smart_transport.py test_ssh_transport.py-20060608202016-c25gvf1ob7ypbus6-2
  bzrlib/tests/test_transport_implementations.py test_transport_implementations.py-20051227111451-f97c5c7d5c49fce7
  bzrlib/transport/__init__.py   transport.py-20050711165921-4978aa7ce1285ad5
  bzrlib/transport/chroot.py     chroot.py-20061011104729-0us9mgm97z378vnt-1
  bzrlib/transport/decorator.py  decorator.py-20060402223305-e913a0f25319ab42
  bzrlib/transport/ftp.py        ftp.py-20051116161804-58dc9506548c2a53
  bzrlib/transport/http/__init__.py http_transport.py-20050711212304-506c5fd1059ace96
  bzrlib/transport/local.py      local_transport.py-20050711165921-9b1f142bfe480c24
  bzrlib/transport/memory.py     memory.py-20051016101338-cd008dbdf69f04fc
  bzrlib/transport/remote.py     ssh.py-20060608202016-c25gvf1ob7ypbus6-1
  bzrlib/transport/sftp.py       sftp.py-20051019050329-ab48ce71b7e32dfe
    ------------------------------------------------------------
    revno: 2586.1.4
    merged: robertc at robertcollins.net-20070705205402-zlb38672nxlb0jsl
    parent: robertc at robertcollins.net-20070705070026-5bfnbk99qfv6yv9i
    committer: Robert Collins <robertc at robertcollins.net>
    branch nick: integration
    timestamp: Fri 2007-07-06 06:54:02 +1000
    message:
      Missed one test.
    ------------------------------------------------------------
    revno: 2586.1.3
    merged: robertc at robertcollins.net-20070705070026-5bfnbk99qfv6yv9i
    parent: robertc at robertcollins.net-20070705023829-5l21034oalccsz7h
    committer: Robert Collins <robertc at robertcollins.net>
    branch nick: external_url
    timestamp: Thu 2007-07-05 17:00:26 +1000
    message:
      Increase comment clarity from review.
    ------------------------------------------------------------
    revno: 2586.1.2
    merged: robertc at robertcollins.net-20070705023829-5l21034oalccsz7h
    parent: robertc at robertcollins.net-20070705010911-q93psu3cxxhn8isr
    committer: Robert Collins <robertc at robertcollins.net>
    branch nick: external_url
    timestamp: Thu 2007-07-05 12:38:29 +1000
    message:
      * The ``SmartServer`` hooks API has changed for the ``server_started`` and
        ``server_stopped`` hooks. The first parameter is now an iterable of
        backing URLs rather than a single URL. This is to reflect that many
        URLs may map to the external URL of the server. E.g. the server interally
        may have a chrooted URL but also the local file:// URL will be at the 
        same location. (Robert Collins)
    ------------------------------------------------------------
    revno: 2586.1.1
    merged: robertc at robertcollins.net-20070705010911-q93psu3cxxhn8isr
    parent: pqm at pqm.ubuntu.com-20070704204059-a1x9jomep52m9arn
    committer: Robert Collins <robertc at robertcollins.net>
    branch nick: external_url
    timestamp: Thu 2007-07-05 11:09:11 +1000
    message:
      * New method ``external_url`` on Transport for obtaining the url to
        hand to external processes. (Robert Collins)
=== modified file 'NEWS'
--- a/NEWS	2007-07-05 10:11:46 +0000
+++ b/NEWS	2007-07-05 22:42:07 +0000
@@ -129,6 +129,13 @@
 
     * ``LockDir.wait`` removed.  (Martin Pool)
 
+    * The ``SmartServer`` hooks API has changed for the ``server_started`` and
+      ``server_stopped`` hooks. The first parameter is now an iterable of
+      backing URLs rather than a single URL. This is to reflect that many
+      URLs may map to the external URL of the server. E.g. the server interally
+      may have a chrooted URL but also the local file:// URL will be at the 
+      same location. (Robert Collins)
+
   INTERNALS:
 
     * New SMTPConnection class to unify email handling.  (Adeodato Simó)
@@ -152,6 +159,9 @@
       deprecated in favour of ``MutableTree.smart_add``. (Robert Collins,
       Martin Pool)
 
+    * New method ``external_url`` on Transport for obtaining the url to
+      hand to external processes. (Robert Collins)
+
   TESTING:
 
     * Removed the ``--keep-output`` option from selftest and clean up test

=== modified file 'bzrlib/errors.py'
--- a/bzrlib/errors.py	2007-07-04 08:08:13 +0000
+++ b/bzrlib/errors.py	2007-07-05 22:42:07 +0000
@@ -198,6 +198,15 @@
         self.current = current
 
 
+class InProcessTransport(BzrError):
+
+    _fmt = "The transport '%(transport)s' is only accessible within this " \
+        "process."
+
+    def __init__(self, transport):
+        self.transport = transport
+
+
 class InvalidEntryName(BzrError):
     
     _fmt = "Invalid entry name: %(name)s"

=== modified file 'bzrlib/smart/server.py'
--- a/bzrlib/smart/server.py	2007-04-20 05:11:46 +0000
+++ b/bzrlib/smart/server.py	2007-07-05 07:00:26 +0000
@@ -22,6 +22,7 @@
 
 from bzrlib.hooks import Hooks
 from bzrlib import (
+    errors,
     trace,
     transport,
 )
@@ -65,8 +66,30 @@
 
     def serve(self):
         self._should_terminate = False
+        # for hooks we are letting code know that a server has started (and
+        # later stopped).
+        # There are three interesting urls:
+        # The URL the server can be contacted on. (e.g. bzr://host/)
+        # The URL that a commit done on the same machine as the server will
+        # have within the servers space. (e.g. file:///home/user/source)
+        # The URL that will be given to other hooks in the same process -
+        # the URL of the backing transport itself. (e.g. chroot+:///)
+        # We need all three because:
+        #  * other machines see the first
+        #  * local commits on this machine should be able to be mapped to
+        #    this server 
+        #  * commits the server does itself need to be mapped across to this
+        #    server.
+        # The latter two urls are different aliases to the servers url,
+        # so we group those in a list - as there might be more aliases 
+        # in the future.
+        backing_urls = [self.backing_transport.base]
+        try:
+            backing_urls.append(self.backing_transport.external_url())
+        except errors.InProcessTransport:
+            pass
         for hook in SmartTCPServer.hooks['server_started']:
-            hook(self.backing_transport.base, self.get_url())
+            hook(backing_urls, self.get_url())
         self._started.set()
         try:
             try:
@@ -100,7 +123,7 @@
                 # ignore errors on close
                 pass
             for hook in SmartTCPServer.hooks['server_stopped']:
-                hook(self.backing_transport.base, self.get_url())
+                hook(backing_urls, self.get_url())
 
     def get_url(self):
         """Return the url of the server"""
@@ -163,11 +186,11 @@
         Hooks.__init__(self)
         # Introduced in 0.16:
         # invoked whenever the server starts serving a directory.
-        # The api signature is (backing url, public url).
+        # The api signature is (backing urls, public url).
         self['server_started'] = []
         # Introduced in 0.16:
         # invoked whenever the server stops serving a directory.
-        # The api signature is (backing url, public url).
+        # The api signature is (backing urls, public url).
         self['server_stopped'] = []
 
 SmartTCPServer.hooks = SmartServerHooks()

=== modified file 'bzrlib/tests/test_errors.py'
--- a/bzrlib/tests/test_errors.py	2007-07-04 08:08:13 +0000
+++ b/bzrlib/tests/test_errors.py	2007-07-05 22:42:07 +0000
@@ -53,6 +53,12 @@
             'It supports versions "(4, 5, 6)" to "(7, 8, 9)".',
             str(error))
 
+    def test_in_process_transport(self):
+        error = errors.InProcessTransport('fpp')
+        self.assertEqualDiff(
+            "The transport 'fpp' is only accessible within this process.",
+            str(error))
+
     def test_inventory_modified(self):
         error = errors.InventoryModified("a tree to be repred")
         self.assertEqualDiff("The current inventory for the tree 'a tree to "

=== modified file 'bzrlib/tests/test_smart_transport.py'
--- a/bzrlib/tests/test_smart_transport.py	2007-04-27 02:19:11 +0000
+++ b/bzrlib/tests/test_smart_transport.py	2007-07-05 20:54:02 +0000
@@ -834,6 +834,8 @@
         self._captureVar('BZR_NO_SMART_VFS', None)
         class FlakyTransport(object):
             base = 'a_url'
+            def external_url(self):
+                return self.base
             def get_bytes(self, path):
                 raise Exception("some random exception from inside server")
         smart_server = server.SmartTCPServer(backing_transport=FlakyTransport())
@@ -860,12 +862,15 @@
     the server is obtained by calling self.setUpServer(readonly=False).
     """
 
-    def setUpServer(self, readonly=False):
+    def setUpServer(self, readonly=False, backing_transport=None):
         """Setup the server.
 
         :param readonly: Create a readonly server.
         """
-        self.backing_transport = memory.MemoryTransport()
+        if not backing_transport:
+            self.backing_transport = memory.MemoryTransport()
+        else:
+            self.backing_transport = backing_transport
         if readonly:
             self.real_backing_transport = self.backing_transport
             self.backing_transport = get_transport("readonly+" + self.backing_transport.abspath('.'))
@@ -998,29 +1003,66 @@
 
 class TestServerHooks(SmartTCPTests):
 
-    def capture_server_call(self, backing_url, public_url):
+    def capture_server_call(self, backing_urls, public_url):
         """Record a server_started|stopped hook firing."""
-        self.hook_calls.append((backing_url, public_url))
-
-    def test_server_started_hook(self):
-        """The server_started hook fires when the server is started."""
-        self.hook_calls = []
-        server.SmartTCPServer.hooks.install_hook('server_started',
-            self.capture_server_call)
-        self.setUpServer()
-        # at this point, the server will be starting a thread up.
-        # there is no indicator at the moment, so bodge it by doing a request.
-        self.transport.has('.')
-        self.assertEqual([(self.backing_transport.base, self.transport.base)],
-            self.hook_calls)
-
-    def test_server_stopped_hook_simple(self):
-        """The server_stopped hook fires when the server is stopped."""
-        self.hook_calls = []
-        server.SmartTCPServer.hooks.install_hook('server_stopped',
-            self.capture_server_call)
-        self.setUpServer()
-        result = [(self.backing_transport.base, self.transport.base)]
+        self.hook_calls.append((backing_urls, public_url))
+
+    def test_server_started_hook_memory(self):
+        """The server_started hook fires when the server is started."""
+        self.hook_calls = []
+        server.SmartTCPServer.hooks.install_hook('server_started',
+            self.capture_server_call)
+        self.setUpServer()
+        # at this point, the server will be starting a thread up.
+        # there is no indicator at the moment, so bodge it by doing a request.
+        self.transport.has('.')
+        # The default test server uses MemoryTransport and that has no external
+        # url:
+        self.assertEqual([([self.backing_transport.base], self.transport.base)],
+            self.hook_calls)
+
+    def test_server_started_hook_file(self):
+        """The server_started hook fires when the server is started."""
+        self.hook_calls = []
+        server.SmartTCPServer.hooks.install_hook('server_started',
+            self.capture_server_call)
+        self.setUpServer(backing_transport=get_transport("."))
+        # at this point, the server will be starting a thread up.
+        # there is no indicator at the moment, so bodge it by doing a request.
+        self.transport.has('.')
+        # The default test server uses MemoryTransport and that has no external
+        # url:
+        self.assertEqual([([
+            self.backing_transport.base, self.backing_transport.external_url()],
+             self.transport.base)],
+            self.hook_calls)
+
+    def test_server_stopped_hook_simple_memory(self):
+        """The server_stopped hook fires when the server is stopped."""
+        self.hook_calls = []
+        server.SmartTCPServer.hooks.install_hook('server_stopped',
+            self.capture_server_call)
+        self.setUpServer()
+        result = [([self.backing_transport.base], self.transport.base)]
+        # check the stopping message isn't emitted up front.
+        self.assertEqual([], self.hook_calls)
+        # nor after a single message
+        self.transport.has('.')
+        self.assertEqual([], self.hook_calls)
+        # clean up the server
+        self.tearDownServer()
+        # now it should have fired.
+        self.assertEqual(result, self.hook_calls)
+
+    def test_server_stopped_hook_simple_file(self):
+        """The server_stopped hook fires when the server is stopped."""
+        self.hook_calls = []
+        server.SmartTCPServer.hooks.install_hook('server_stopped',
+            self.capture_server_call)
+        self.setUpServer(backing_transport=get_transport("."))
+        result = [(
+            [self.backing_transport.base, self.backing_transport.external_url()]
+            , self.transport.base)]
         # check the stopping message isn't emitted up front.
         self.assertEqual([], self.hook_calls)
         # nor after a single message

=== modified file 'bzrlib/tests/test_transport_implementations.py'
--- a/bzrlib/tests/test_transport_implementations.py	2007-07-04 08:08:13 +0000
+++ b/bzrlib/tests/test_transport_implementations.py	2007-07-05 22:42:07 +0000
@@ -135,6 +135,14 @@
         t_b = t_a.clone('b')
         self.assertRaises(NoSuchFile, t_b.ensure_base)
 
+    def test_external_url(self):
+        """.external_url either works or raises InProcessTransport."""
+        t = self.get_transport()
+        try:
+            t.external_url()
+        except errors.InProcessTransport:
+            pass
+
     def test_has(self):
         t = self.get_transport()
 

=== modified file 'bzrlib/transport/__init__.py'
--- a/bzrlib/transport/__init__.py	2007-07-05 00:00:57 +0000
+++ b/bzrlib/transport/__init__.py	2007-07-05 22:42:07 +0000
@@ -317,6 +317,28 @@
         else:
             return True
 
+    def external_url(self):
+        """Return a URL for self that can be given to an external process.
+
+        There is no guarantee that the URL can be accessed from a different
+        machine - e.g. file:/// urls are only usable on the local machine,
+        sftp:/// urls when the server is only bound to localhost are only
+        usable from localhost etc.
+
+        NOTE: This method may remove security wrappers (e.g. on chroot
+        transports) and thus should *only* be used when the result will not
+        be used to obtain a new transport within bzrlib. Ideally chroot
+        transports would know enough to cause the external url to be the exact
+        one used that caused the chrooting in the first place, but that is not
+        currently the case.
+
+        :return: A URL that can be given to another process.
+        :raises InProcessTransport: If the transport is one that cannot be
+            accessed out of the current process (e.g. a MemoryTransport)
+            then InProcessTransport is raised.
+        """
+        raise NotImplementedError(self.external_url)
+
     def should_cache(self):
         """Return True if the data pulled across should be cached locally.
         """

=== modified file 'bzrlib/transport/chroot.py'
--- a/bzrlib/transport/chroot.py	2007-04-13 14:23:26 +0000
+++ b/bzrlib/transport/chroot.py	2007-07-05 01:09:11 +0000
@@ -98,6 +98,15 @@
     def delete_tree(self, relpath):
         return self._call('delete_tree', relpath)
 
+    def external_url(self):
+        """See bzrlib.transport.Transport.external_url."""
+        # Chroots, like MemoryTransport depend on in-process
+        # state and thus the base cannot simply be handed out.
+        # See the base class docstring for more details and
+        # possible directions. For now we return the chrooted
+        # url. 
+        return self.server.backing_transport.external_url()
+
     def get(self, relpath):
         return self._call('get', relpath)
 

=== modified file 'bzrlib/transport/decorator.py'
--- a/bzrlib/transport/decorator.py	2007-04-05 09:35:26 +0000
+++ b/bzrlib/transport/decorator.py	2007-07-05 01:09:11 +0000
@@ -79,6 +79,13 @@
         """See Transport.delete_tree()."""
         return self._decorated.delete_tree(relpath)
 
+    def external_url(self):
+        """See bzrlib.transport.Transport.external_url."""
+        # while decorators are in-process only, they
+        # can be handed back into bzrlib safely, so
+        # its just the base.
+        return self.base
+
     @classmethod
     def _get_url_prefix(self):
         """Return the URL prefix of this decorator."""

=== modified file 'bzrlib/transport/ftp.py'
--- a/bzrlib/transport/ftp.py	2007-07-04 08:08:13 +0000
+++ b/bzrlib/transport/ftp.py	2007-07-05 22:42:07 +0000
@@ -489,6 +489,11 @@
             self._translate_perm_error(e, abspath, 'error deleting',
                 unknown_exc=errors.NoSuchFile)
 
+    def external_url(self):
+        """See bzrlib.transport.Transport.external_url."""
+        # FTP URL's are externally usable.
+        return self.base
+
     def listable(self):
         """See Transport.listable."""
         return True

=== modified file 'bzrlib/transport/http/__init__.py'
--- a/bzrlib/transport/http/__init__.py	2007-07-03 07:03:32 +0000
+++ b/bzrlib/transport/http/__init__.py	2007-07-05 01:09:11 +0000
@@ -439,6 +439,11 @@
         """Delete the item at relpath"""
         raise errors.TransportNotPossible('http does not support delete()')
 
+    def external_url(self):
+        """See bzrlib.transport.Transport.external_url."""
+        # HTTP URL's are externally usable.
+        return self.base
+
     def is_readonly(self):
         """See Transport.is_readonly."""
         return True

=== modified file 'bzrlib/transport/local.py'
--- a/bzrlib/transport/local.py	2007-07-04 08:08:13 +0000
+++ b/bzrlib/transport/local.py	2007-07-05 22:42:07 +0000
@@ -393,6 +393,11 @@
         except (IOError, OSError),e:
             self._translate_error(e, path)
 
+    def external_url(self):
+        """See bzrlib.transport.Transport.external_url."""
+        # File URL's are externally usable.
+        return self.base
+
     def copy_to(self, relpaths, other, mode=None, pb=None):
         """Copy a set of entries from self into another Transport.
 

=== modified file 'bzrlib/transport/memory.py'
--- a/bzrlib/transport/memory.py	2007-07-04 08:08:13 +0000
+++ b/bzrlib/transport/memory.py	2007-07-05 22:42:07 +0000
@@ -27,7 +27,13 @@
 from cStringIO import StringIO
 import warnings
 
-from bzrlib.errors import TransportError, NoSuchFile, FileExists, LockError
+from bzrlib.errors import (
+    FileExists,
+    LockError,
+    InProcessTransport,
+    NoSuchFile,
+    TransportError,
+    )
 from bzrlib.trace import mutter
 from bzrlib.transport import (
     LateReadError,
@@ -122,6 +128,12 @@
             raise NoSuchFile(relpath)
         del self._files[_abspath]
 
+    def external_url(self):
+        """See bzrlib.transport.Transport.external_url."""
+        # MemoryTransport's are only accessible in-process
+        # so we raise here
+        raise InProcessTransport(self)
+
     def get(self, relpath):
         """See Transport.get()."""
         _abspath = self._abspath(relpath)

=== modified file 'bzrlib/transport/remote.py'
--- a/bzrlib/transport/remote.py	2007-07-04 08:08:13 +0000
+++ b/bzrlib/transport/remote.py	2007-07-05 22:42:07 +0000
@@ -291,6 +291,11 @@
         resp = self._call2('delete', self._remote_path(relpath))
         self._translate_error(resp)
 
+    def external_url(self):
+        """See bzrlib.transport.Transport.external_url."""
+        # the external path for RemoteTransports is the base
+        return self.base
+
     def readv(self, relpath, offsets):
         if not offsets:
             return

=== modified file 'bzrlib/transport/sftp.py'
--- a/bzrlib/transport/sftp.py	2007-07-04 23:42:40 +0000
+++ b/bzrlib/transport/sftp.py	2007-07-05 22:42:07 +0000
@@ -764,6 +764,11 @@
         except (IOError, paramiko.SSHException), e:
             self._translate_io_exception(e, path, ': unable to delete')
             
+    def external_url(self):
+        """See bzrlib.transport.Transport.external_url."""
+        # the external path for SFTP is the base
+        return self.base
+
     def listable(self):
         """Return True if this store supports listing."""
         return True




More information about the bazaar-commits mailing list