[Checkins] SVN: zc.zk/trunk/s Improved ``register_server`` in the case when an empty host is

Jim Fulton jim at zope.com
Wed Jan 25 20:20:28 UTC 2012


Log message for revision 124172:
  Improved ``register_server`` in the case when an empty host is
  passed.  If `netifaces
  <http://alastairs-place.net/projects/netifaces/>`_ is installed,
  ``register_server`` registers all of the IPv4 addresses [#ifaces]_.
  

Changed:
  U   zc.zk/trunk/setup.py
  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/setup.py
===================================================================
--- zc.zk/trunk/setup.py	2012-01-25 15:14:41 UTC (rev 124171)
+++ zc.zk/trunk/setup.py	2012-01-25 20:20:27 UTC (rev 124172)
@@ -16,7 +16,7 @@
 install_requires = ['setuptools', 'zc.thread']
 extras_require = dict(
     test=['zope.testing', 'zc-zookeeper-static', 'mock', 'manuel',
-          'zope.event'],
+          'zope.event', 'netifaces'],
     static=['zc-zookeeper-static'],
     )
 

Modified: zc.zk/trunk/src/zc/zk/README.txt
===================================================================
--- zc.zk/trunk/src/zc/zk/README.txt	2012-01-25 15:14:41 UTC (rev 124171)
+++ zc.zk/trunk/src/zc/zk/README.txt	2012-01-25 20:20:27 UTC (rev 124172)
@@ -636,7 +636,6 @@
 ommitted, then the refering name is used.  For example, the name could
 be left off of the property link above.
 
-
 Node deletion
 =============
 
@@ -652,16 +651,14 @@
 ==========================================
 
 It's common to use an empty string for a host name when calling bind
-to listen on all IPv4 interfaces.  If you pass an empty host name to
-``register_server``, the result of calling ``socket.getfqdn()`` will
-be registered::
+to listen on all IPv4 interfaces.  If you pass an address with an
+empty host to ``register_server`` and `netifaces
+<http://alastairs-place.net/projects/netifaces/>`_ is installed, then
+all of the IPv4 addresses [#ifaces]_ (for the given port) will be
+registered.  If netifaces isn't installed and you pass an empty host
+name, then the fully-qualified domain name, as returned by
+``socket.getfqdn()`` will be used for the host.
 
-    >>> zk.register_server('/fooservice/providers', ('', 42))
-    addresses changed
-    ['192.168.0.42:8080', '192.168.0.42:8081', '192.168.0.42:8082',
-     'server.example.com:42']
-
-
 Server-registration events
 ==========================
 
@@ -713,8 +710,6 @@
           pid = 7981
         /192.168.0.42:8082
           pid = 7981
-        /server.example.com:42
-          pid = 7981
 
 .. -> sh
 
@@ -1026,6 +1021,11 @@
 0.6.0 (2012-01-??)
 ------------------
 
+- Improved ``register_server`` in the case when an empty host is
+  passed.  If `netifaces
+  <http://alastairs-place.net/projects/netifaces/>`_ is installed,
+  ``register_server`` registers all of the IPv4 addresses [#ifaces]_.
+
 - ``delete_recursive`` now has a ``force`` argument to force deletion of
   ephemeral nodes.
 
@@ -1132,3 +1132,15 @@
 .. test cleanup
 
    >>> zk.close()
+
+
+----------------------------------------------------------------------
+
+.. [#ifaces] It's a little more complicated.  If there are non-local
+   interfaces, then only non-local addresses are registered.  In
+   normal production, there's really no point in registering local
+   addresses, as clients on other machines can't make any sense of
+   them. If *only* local interfaces are found, then local addresses
+   are registered, under the assumption that someone is developing on
+   a disconnected computer.
+

Modified: zc.zk/trunk/src/zc/zk/__init__.py
===================================================================
--- zc.zk/trunk/src/zc/zk/__init__.py	2012-01-25 15:14:41 UTC (rev 124171)
+++ zc.zk/trunk/src/zc/zk/__init__.py	2012-01-25 20:20:27 UTC (rev 124172)
@@ -207,21 +207,44 @@
                 raise FailedConnect(connection_string)
 
 
+    def _findallipv4addrs(self, tail):
+        try:
+            import netifaces
+        except ImportError:
+            return [socket.getfqdn()+tail]
+
+        addrs = set()
+        loopaddrs = set()
+        for iface in netifaces.interfaces():
+            for info in netifaces.ifaddresses(iface).get(2, ()):
+                addr = info.get('addr')
+                if addr:
+                    if addr.startswith('127.'):
+                        loopaddrs.add(addr+tail)
+                    else:
+                        addrs.add(addr+tail)
+
+        return addrs or loopaddrs
+
     def register_server(self, path, addr, acl=READ_ACL_UNSAFE, **kw):
         kw['pid'] = os.getpid()
-        if isinstance(addr, str):
-            if addr[:1] == ':':
-                addr = socket.getfqdn()+addr
+
+        if not isinstance(addr, str):
+            addr = '%s:%s' % tuple(addr)
+
+        if addr[:1] == ':':
+            addrs = self._findallipv4addrs(addr)
         else:
-            if addr[0] == '':
-                addr = socket.getfqdn(), addr[1]
-            addr = '%s:%s' % addr
+            addrs = (addr,)
+
         path = self.resolve(path)
         zc.zk.event.notify(RegisteringServer(addr, path, kw))
         if path != '/':
             path += '/'
-        self.create(path + addr, encode(kw), acl, zookeeper.EPHEMERAL)
 
+        for addr in addrs:
+            self.create(path + addr, encode(kw), acl, zookeeper.EPHEMERAL)
+
     test_sleep = 0
     def _async(self, completion, meth, *args):
         post = getattr(self, '_post_'+meth)

Modified: zc.zk/trunk/src/zc/zk/tests.py
===================================================================
--- zc.zk/trunk/src/zc/zk/tests.py	2012-01-25 15:14:41 UTC (rev 124171)
+++ zc.zk/trunk/src/zc/zk/tests.py	2012-01-25 20:20:27 UTC (rev 124172)
@@ -21,6 +21,7 @@
 import mock
 import os
 import re
+import socket
 import StringIO
 import sys
 import threading
@@ -135,7 +136,124 @@
             self.assertEqual(flags, zookeeper.EPHEMERAL)
 
         self.__zk.register_server('/foo', ('127.0.0.1', 8080), a=1)
+        self.__zk.register_server('/foo', '127.0.0.1:8080', a=1)
 
+    @mock.patch('netifaces.ifaddresses')
+    @mock.patch('netifaces.interfaces')
+    @mock.patch('zookeeper.create')
+    def test_register_server_blank(self, create, interfaces, ifaddresses):
+        addrs = {
+            'eth0': {2: [{'addr': '192.168.24.60',
+                          'broadcast': '192.168.24.255',
+                          'netmask': '255.255.255.0'}],
+                     10: [{'addr': 'fe80::21c:c0ff:fe1a:d12%eth0',
+                           'netmask': 'ffff:ffff:ffff:ffff::'}],
+                     17: [{'addr': '00:1c:c0:1a:0d:12',
+                           'broadcast': 'ff:ff:ff:ff:ff:ff'}]},
+            'foo': {2: [{'addr': '192.168.24.61',
+                         'broadcast': '192.168.24.255',
+                         'netmask': '255.255.255.0'}],
+                    },
+            'lo': {2: [{'addr': '127.0.0.1',
+                        'netmask': '255.0.0.0',
+                        'peer': '127.0.0.1'}],
+                   10: [{'addr': '::1',
+                         'netmask': 'ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff'}],
+                   17: [{'addr': '00:00:00:00:00:00',
+                         'peer': '00:00:00:00:00:00'}],
+                   },
+            }
+
+        @side_effect(interfaces)
+        def _():
+            return list(addrs)
+
+        @side_effect(ifaddresses)
+        def _(iface):
+            return addrs[iface]
+
+        @side_effect(create)
+        def _(handle, path_, data, acl, flags):
+            self.assertEqual(handle, 0)
+            paths.append(path_)
+            self.assertEqual(json.loads(data), dict(pid=os.getpid(), a=1))
+            self.assertEqual(acl, [zc.zk.world_permission()])
+            self.assertEqual(flags, zookeeper.EPHEMERAL)
+
+        paths = []
+        self.__zk.register_server('/foo', ('', 8080), a=1)
+        self.assertEqual(sorted(paths),
+                         ['/foo/192.168.24.60:8080', '/foo/192.168.24.61:8080'])
+
+        paths = []
+        self.__zk.register_server('/foo', ':8080', a=1)
+        self.assertEqual(sorted(paths),
+                         ['/foo/192.168.24.60:8080', '/foo/192.168.24.61:8080'])
+
+    @mock.patch('netifaces.ifaddresses')
+    @mock.patch('netifaces.interfaces')
+    @mock.patch('zookeeper.create')
+    def test_register_server_blank_nonet(self, create, interfaces, ifaddresses):
+        addrs = {
+            'lo': {2: [{'addr': '127.0.0.1',
+                        'netmask': '255.0.0.0',
+                        'peer': '127.0.0.1'}],
+                   10: [{'addr': '::1',
+                         'netmask': 'ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff'}],
+                   17: [{'addr': '00:00:00:00:00:00',
+                         'peer': '00:00:00:00:00:00'}]},
+            }
+
+        @side_effect(interfaces)
+        def _():
+            return list(addrs)
+
+        @side_effect(ifaddresses)
+        def _(iface):
+            return addrs[iface]
+
+        @side_effect(create)
+        def _(handle, path_, data, acl, flags):
+            self.assertEqual(handle, 0)
+            paths.append(path_)
+            self.assertEqual(json.loads(data), dict(pid=os.getpid(), a=1))
+            self.assertEqual(acl, [zc.zk.world_permission()])
+            self.assertEqual(flags, zookeeper.EPHEMERAL)
+
+        paths = []
+        self.__zk.register_server('/foo', ('', 8080), a=1)
+        self.assertEqual(sorted(paths), ['/foo/127.0.0.1:8080'])
+
+        paths = []
+        self.__zk.register_server('/foo', ':8080', a=1)
+        self.assertEqual(sorted(paths), ['/foo/127.0.0.1:8080'])
+
+    @mock.patch('zookeeper.create')
+    @mock.patch('socket.getfqdn')
+    def test_register_server_blank_nonetifaces(self, getfqdn, create):
+
+        netifaces = sys.modules['netifaces']
+        try:
+            sys.modules['netifaces'] = None
+
+            @side_effect(getfqdn)
+            def _():
+                return 'nonexistenttestserver.zope.com'
+
+            @side_effect(create)
+            def _(handle, path_, data, acl, flags):
+                self.assertEqual(
+                    (handle, path_),
+                    (0, '/foo/nonexistenttestserver.zope.com:8080'))
+                self.assertEqual(json.loads(data), dict(pid=os.getpid(), a=1))
+                self.assertEqual(acl, [zc.zk.world_permission()])
+                self.assertEqual(flags, zookeeper.EPHEMERAL)
+
+            self.__zk.register_server('/foo', ('', 8080), a=1)
+            self.__zk.register_server('/foo', ':8080', a=1)
+        finally:
+            sys.modules['netifaces'] = netifaces
+
     @mock.patch('zookeeper.close')
     @mock.patch('zookeeper.init')
     @mock.patch('zookeeper.state')



More information about the checkins mailing list