[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