[Checkins] SVN: zc.zk/trunk/src/zc/zk/ Added symbolic-link handling.

Jim Fulton jim at zope.com
Fri Dec 2 23:05:51 UTC 2011


Log message for revision 123563:
  Added symbolic-link handling.
  

Changed:
  U   zc.zk/trunk/src/zc/zk/README.txt
  U   zc.zk/trunk/src/zc/zk/__init__.py
  U   zc.zk/trunk/src/zc/zk/tests.py

-=-
Modified: zc.zk/trunk/src/zc/zk/README.txt
===================================================================
--- zc.zk/trunk/src/zc/zk/README.txt	2011-12-02 17:54:28 UTC (rev 123562)
+++ zc.zk/trunk/src/zc/zk/README.txt	2011-12-02 23:05:50 UTC (rev 123563)
@@ -421,7 +421,33 @@
     /fooservice/providers not deleted due to ephemeral descendent.
     /fooservice not deleted due to ephemeral descendent.
 
+Symbolic links
+--------------
 
+ZooKeeper doesn't have a concept of symbolic links, but ``zc.zk``
+provides a convention for dealing with symbolic links.  When trying to
+resolve a path, if a node lacks a child, but hase a property with a
+name ending in ``' ->'``, the child will be found by following the
+path in the property value.
+
+The ``resolve`` method is used to resolve a path to a real path::
+
+    >>> zk.resolve('/lb/pools/cms/providers')
+    u'/cms/providers'
+
+In this example, the link was at the endpoint of the virtual path, but
+it could be anywhere::
+
+    >>> zk.register_server('/cms/providers', '1.2.3.4:5')
+    >>> zk.resolve('/lb/pools/cms/providers/1.2.3.4:5')
+    u'/cms/providers/1.2.3.4:5'
+
+Note a limitation of symbolic links is that they can be hidden by
+children.  For example, if we added a real node, at ``/lb/pools``
+
+``children``, ``properties``, and ``register_server`` will
+automatically use ``resolve`` to resolve paths.
+
 ``zc.zk.ZooKeeper``
 -------------------
 
@@ -480,6 +506,9 @@
    The dry_run option causes a summary of what would be deleted to be
    printed without actually deleting anything.
 
+``resolve(path)``
+   Find the real path for the given path.
+
 ``register_server(path, address, **data)``
     Register a server at a path with the address.
 
@@ -559,7 +588,7 @@
 Changes
 -------
 
-0.2.0 (2011-12-??)
+0.2.0 (2011-12-)
 ~~~~~~~~~~~~~~~~~~
 
 - Added tree import and export.

Modified: zc.zk/trunk/src/zc/zk/__init__.py
===================================================================
--- zc.zk/trunk/src/zc/zk/__init__.py	2011-12-02 17:54:28 UTC (rev 123562)
+++ zc.zk/trunk/src/zc/zk/__init__.py	2011-12-02 23:05:50 UTC (rev 123563)
@@ -105,6 +105,9 @@
 class CancelWatch(Exception):
     pass
 
+class LinkLoop(Exception):
+    pass
+
 class ZooKeeper:
 
     def __init__(self, zkaddr=2181):
@@ -148,6 +151,7 @@
         if not isinstance(addr, str):
             addr = '%s:%s' % addr
         self.connected.wait()
+        path = self.resolve(path)
         zookeeper.create(self.handle, path + '/' + addr, encode(kw),
                          [world_permission()], zookeeper.EPHEMERAL)
 
@@ -355,6 +359,29 @@
     def properties(self, path):
         return Properties(self, path)
 
+    def resolve(self, path, seen=()):
+        if self.exists(path):
+            return path
+        if path in seen:
+            seen += (path,)
+            raise LinkLoop(seen)
+
+        try:
+            base, name = path.rsplit('/', 1)
+            base = self.resolve(base, seen)
+            newpath = base + '/' + name
+            if self.exists(newpath):
+                return newpath
+            props = decode(self.get(base)[0])
+            newpath = props.get(name+' ->')
+            if not newpath:
+                raise zookeeper.NoNodeException()
+
+            seen += (path,)
+            return self.resolve(newpath, seen)
+        except zookeeper.NoNodeException:
+            raise zookeeper.NoNodeException(path)
+
     def _set(self, path, data):
         self.connected.wait()
         return zookeeper.set(self.handle, path, data)
@@ -391,7 +418,7 @@
 
     def __init__(self, session, path):
         self.session = session
-        self.path = path
+        self.path = session.resolve(path)
         self.callbacks = set()
         session._watch(self)
 

Modified: zc.zk/trunk/src/zc/zk/tests.py
===================================================================
--- zc.zk/trunk/src/zc/zk/tests.py	2011-12-02 17:54:28 UTC (rev 123562)
+++ zc.zk/trunk/src/zc/zk/tests.py	2011-12-02 23:05:50 UTC (rev 123563)
@@ -78,6 +78,16 @@
         self.__zk = getzk.value
         self.assertEqual(self.__zk.handle, 0)
 
+        self.__teardowns = []
+        cm = mock.patch('zookeeper.exists')
+        @side_effect(cm.__enter__())
+        def exists(handle, path):
+            return True
+
+    def tearDown(self):
+        while self.__teardowns:
+            self.__teardowns.pop()()
+
     def state_side_effect(self, handle):
         self.assertEqual(handle, self.__zk.handle)
         return zookeeper.CONNECTED_STATE
@@ -490,6 +500,62 @@
     {u'a': 2, u'b': 1, u'c': 1, u'd': 2, u'z': 1}
     """
 
+def test_resolve():
+    """
+    >>> zk = zc.zk.ZooKeeper('zookeeper.example.com:2181')
+    >>> zk.import_tree('''
+    ... /top
+    ...  /a
+    ...    top -> /top
+    ...    loop -> /top/a/b/loop
+    ...    /b
+    ...      top -> /top
+    ...      loop -> /top/a/loop
+    ...      /c
+    ...        top -> /top
+    ...        /d
+    ...          name = 'd'
+    ...          /e
+    ...          top -> /top
+    ...          loop -> /top/a/b/c/d/loop
+    ...
+    ... ''')
+
+
+    >>> zk.resolve('/top/a/b/c/d')
+    '/top/a/b/c/d'
+
+    >>> zk.resolve('/top/a/top/a/b/top/a/b/c/top/a/b/c/d')
+    u'/top/a/b/c/d'
+
+    >>> sorted(zk.properties('/top/a/top/a/b/top/a/b/c/top/a/b/c/d').items())
+    [(u'loop ->', u'/top/a/b/c/d/loop'), (u'name', u'd'), (u'top ->', u'/top')]
+
+    >>> zk.register_server('/top/a/top/a/b/top/a/b/c/top/a/b/c/d', 'addr')
+    >>> sorted(zk.children('/top/a/top/a/b/top/a/b/c/top/a/b/c/d'))
+    [u'addr', 'e']
+
+    >>> zk.resolve('/top/a/top/a/b/top/x')
+    Traceback (most recent call last):
+      File "/usr/local/python/2.6/lib/python2.6/doctest.py", line 1253, in __run
+        compileflags, 1) in test.globs
+      File "<doctest zc.zk.tests.test_resolve[4]>", line 1, in <module>
+        zk.resolve('/top/a/top/a/b/top/x')
+      File "/Users/jim/p/zc/zk/trunk/src/zc/zk/__init__.py", line 382, in resolve
+        raise zookeeper.NoNodeException(path)
+    NoNodeException: /top/a/top/a/b/top/x
+
+    >>> zk.resolve('/top/a/b/c/d/loop')
+    Traceback (most recent call last):
+    ...
+    LinkLoop: ('/top/a/b/c/d/loop', u'/top/a/b/c/d/loop')
+
+    >>> zk.resolve('/top/a/loop/b/c/d')
+    Traceback (most recent call last):
+    ...
+    LinkLoop: ('/top/a/loop', u'/top/a/b/loop', u'/top/a/loop')
+    """
+
 def assert_(cond, mess=''):
     if not cond:
         print 'assertion failed: ', mess



More information about the checkins mailing list