[Checkins] SVN: Sandbox/J1m/zkzopeserver/ Added zc.monitor support.

Jim Fulton jim at zope.com
Sun Dec 11 16:36:25 UTC 2011


Log message for revision 123673:
  Added zc.monitor support.
  
  Cleaned up server management in tests.
  

Changed:
  U   Sandbox/J1m/zkzopeserver/buildout.cfg
  U   Sandbox/J1m/zkzopeserver/setup.py
  U   Sandbox/J1m/zkzopeserver/src/zc/zkzopeserver/README.txt
  U   Sandbox/J1m/zkzopeserver/src/zc/zkzopeserver/__init__.py
  U   Sandbox/J1m/zkzopeserver/src/zc/zkzopeserver/tests.py

-=-
Modified: Sandbox/J1m/zkzopeserver/buildout.cfg
===================================================================
--- Sandbox/J1m/zkzopeserver/buildout.cfg	2011-12-11 14:03:42 UTC (rev 123672)
+++ Sandbox/J1m/zkzopeserver/buildout.cfg	2011-12-11 16:36:24 UTC (rev 123673)
@@ -25,3 +25,4 @@
   use = egg:zc.zkzopeserver
   zookeeper = 127.0.0.1:2181
   path = /
+  monitor_server = true

Modified: Sandbox/J1m/zkzopeserver/setup.py
===================================================================
--- Sandbox/J1m/zkzopeserver/setup.py	2011-12-11 14:03:42 UTC (rev 123672)
+++ Sandbox/J1m/zkzopeserver/setup.py	2011-12-11 16:36:24 UTC (rev 123673)
@@ -14,7 +14,9 @@
 name, version = 'zc.zkzopeserver', '0'
 
 install_requires = ['setuptools', 'zc.zk', 'zope.server']
-extras_require = dict(test=['zope.testing', 'zc.zk [static,test]'])
+extras_require = dict(
+    test=['zope.testing', 'zc.zk [static,test]', 'zc.monitor']
+    )
 
 entry_points = """
 [paste.server_runner]

Modified: Sandbox/J1m/zkzopeserver/src/zc/zkzopeserver/README.txt
===================================================================
--- Sandbox/J1m/zkzopeserver/src/zc/zkzopeserver/README.txt	2011-12-11 14:03:42 UTC (rev 123672)
+++ Sandbox/J1m/zkzopeserver/src/zc/zkzopeserver/README.txt	2011-12-11 16:36:24 UTC (rev 123673)
@@ -42,6 +42,14 @@
 threads
    The size of the thread pool, defaulting to 1
 
+monitor_server
+   A ``zc.monitor`` server address.
+
+   The value can be a host name, a port or a host:port address.  If
+   the port isn't specified, it defaults to ``0``. If the host isn't
+   specified, it defaults to ```127.0.0.1```.  The value ``true`` is
+   an alias for ``0``.  See `Monitor server`_ below.
+
 .. test
 
     >>> import ConfigParser, StringIO
@@ -61,8 +69,8 @@
     ... def server():
     ...     run(wsgiref.simple_server.demo_app, {}, **kw)
 
-    >>> import time
-    >>> time.sleep(.1)
+    >>> import zc.zkzopeserver
+    >>> zc.zkzopeserver.event_for_testing.wait(1)
 
     >>> import urllib, zc.zk
     >>> zk = zc.zk.ZooKeeper('zookeeper.example.com:2181')
@@ -74,16 +82,82 @@
     Hello world!
     ...
 
-    Cleanup, sigh, violently close the server.
+    >>> zc.zkzopeserver.stop_for_testing(server)
+    >>> zk.get_children('/fooservice/providers')
+    []
 
-    >>> import asyncore
-    >>> for s in asyncore.socket_map.values():
-    ...     s.close()
-    >>> server.join(1)
-    >>> zk.close()
+Monitor server
+==============
 
+The `zc.monitor <http://pypi.python.org/pypi/zc.monitor>`_ package
+provides a simple extensible command server for gathering monitoring
+data or providing run-time control of servers.  If ``zc.monitor`` is
+in the Python path, ``zc.zkzopeserver`` will start a monitor server
+and make it's address available as the ``monitor`` property of of a
+servers ephemeral port.  To see how this works, let's update the
+earler example::
 
+   [server:main]
+   use = egg:zc.zkzopeserver
+   zookeeper = zookeeper.example.com:2181
+   path = /fooservice/providers
+   monitor_server = true
 
+.. -> server_config
+
+Here we've used ``monitor = true``, which is equivalent to ``0``,
+``127.0.0.1:0``, and ``127.0.0.1``.
+
+When our web server is running, the ``/fooservice/providers`` node
+would look something like::
+
+    /providers
+      /0.0.0.0:61181
+        monitor = u'127.0.0.1:61182'
+        pid = 4525
+
+.. -> expected_tree
+
+    >>> parser = ConfigParser.RawConfigParser()
+    >>> parser.readfp(StringIO.StringIO(server_config))
+    >>> kw = dict(parser.items('server:main'))
+    >>> del kw['use']
+
+    >>> @zc.thread.Thread
+    ... def server():
+    ...     run(wsgiref.simple_server.demo_app, {}, **kw)
+
+    >>> zc.zkzopeserver.event_for_testing.wait(1)
+
+    >>> [port] = [int(c.split(':')[1])
+    ...           for c in zk.get_children('/fooservice/providers')]
+    >>> print urllib.urlopen('http://127.0.0.1:%s/' % port).read()
+    ... # doctest: +ELLIPSIS
+    Hello world!
+    ...
+
+    >>> import re, zope.testing.renormalizing
+    >>> checker = zope.testing.renormalizing.RENormalizing([
+    ...     (re.compile('pid = \d+'), 'pid = 999'),
+    ...     (re.compile('(0\.0\.[01]):\d+'), '\1:99999'),
+    ...     ])
+    >>> actual_tree = zk.export_tree('/fooservice/providers', True)
+    >>> checker.check_output(expected_tree.strip(), actual_tree.strip(), 0)
+    True
+
+    >>> zc.zkzopeserver.stop_for_testing(server)
+    >>> zk.get_children('/fooservice/providers')
+    []
+
+Some notes on the monitor server:
+
+- A momnitor server won't be useful unless you've registered some
+  command plugins.
+
+- ``zc.monitor`` isn't a dependency of ``zc.zkzopeserver`` and won't
+  be in the Python path unless you install it.
+
+
 Change History
 ==============
 
@@ -91,3 +165,8 @@
 ------------------
 
 Initial release
+
+
+.. test cleanup
+
+    >>> zk.close()

Modified: Sandbox/J1m/zkzopeserver/src/zc/zkzopeserver/__init__.py
===================================================================
--- Sandbox/J1m/zkzopeserver/src/zc/zkzopeserver/__init__.py	2011-12-11 14:03:42 UTC (rev 123672)
+++ Sandbox/J1m/zkzopeserver/src/zc/zkzopeserver/__init__.py	2011-12-11 16:36:24 UTC (rev 123673)
@@ -12,13 +12,39 @@
 #
 ##############################################################################
 import asyncore
+import re
+import threading
 import zc.zk
+import zope.server.dualmodechannel
 import zope.server.taskthreads
 import zope.server.http.wsgihttpserver
 
+event_for_testing = threading.Event()
+server_for_testing = None
+
+# Note to the reader and tester
+# -----------------------------
+# zope.server and zc.monitor were, for better or worser, designed to
+# be used as or in main programs. They expect to be instantiated
+# exactly once and to be used through the end of the program.  Neither
+# provide a clean shutdown mechanism.  In normal usage, this probably
+# isn't a problem, but it's awkward for testing.
+#
+# We've worked around this by providing here a way to shutdown a
+# running server, assuming there is only one at a time. :/  See the 3
+# "_for_testing" variables.  We've leveraged a similar mechanism in
+# zc.monitor. See the use of last_listener below.
+
+def stop_for_testing(thread=None):
+    zope.server.dualmodechannel.the_trigger.pull_trigger(
+        server_for_testing.close)
+    event_for_testing.clear()
+    if thread is not None:
+        thread.join(1)
+
 def run(wsgi_app, global_conf,
         zookeeper, path, session_timeout=None,
-        name=__name__, host='', port=0, threads=1,
+        name=__name__, host='', port=0, threads=1, monitor_server=None,
         ):
     port = int(port)
     threads = int(threads)
@@ -28,11 +54,36 @@
     server = zope.server.http.wsgihttpserver.WSGIHTTPServer(
         wsgi_app, name, host, port,
         task_dispatcher=task_dispatcher)
+
+    props = {}
+    if monitor_server:
+        host, port = '127.0.0.1', 0
+        if ':' in monitor_server:
+            host, port = monitor.rsplit(':', 1)
+        elif re.match('\d+$', monitor_server):
+            port = monitor_server
+        elif monitor_server != 'true':
+            host = monitor_server
+        global zc
+        import zc.monitor
+        props['monitor'] = "%s:%s" % zc.monitor.start((host, int(port)))
+
     server.ZooKeeper = zc.zk.ZooKeeper(
         zookeeper, session_timeout and int(session_timeout))
     server.ZooKeeper.register_server(
-        path, "%s:%s" % server.socket.getsockname())
+        path, "%s:%s" % server.socket.getsockname(), **props)
+
+    map = asyncore.socket_map
+    poll_fun = asyncore.poll
+
+    global server_for_testing
+    server_for_testing = server
+    event_for_testing.set()
+
     try:
-        asyncore.loop()
+        while server.accepting:
+            poll_fun(30.0, map)
     finally:
         server.ZooKeeper.close()
+        if monitor_server:
+            zc.monitor.last_listener.close()

Modified: Sandbox/J1m/zkzopeserver/src/zc/zkzopeserver/tests.py
===================================================================
--- Sandbox/J1m/zkzopeserver/src/zc/zkzopeserver/tests.py	2011-12-11 14:03:42 UTC (rev 123672)
+++ Sandbox/J1m/zkzopeserver/src/zc/zkzopeserver/tests.py	2011-12-11 16:36:24 UTC (rev 123673)
@@ -20,6 +20,7 @@
 import time
 import unittest
 import urllib
+#import zc.ngi.async
 import zc.thread
 import zc.zk.testing
 import zope.testing.renormalizing
@@ -30,6 +31,7 @@
     r = maxactive
     maxactive = active = 0
     return r
+
 def slow_app(environ, start_response):
     global active, maxactive
     active += 1
@@ -41,12 +43,6 @@
     active -= 1
     return ['Hello world!\n']
 
-def stop_server(thread):
-    for s in asyncore.socket_map.values():
-        s.close()
-    asyncore.socket_map.clear()
-    thread.join(1)
-
 def test_options():
     """
     Make sure various advertized options work:
@@ -63,7 +59,8 @@
     ...         threads='3',
     ...         )
 
-    >>> time.sleep(.1)
+    >>> import zc.zkzopeserver
+    >>> zc.zkzopeserver.event_for_testing.wait(1)
     >>> zk = zc.zk.ZooKeeper('zookeeper.example.com:2181')
 
     Did it get registered with the given host and port?
@@ -100,7 +97,7 @@
 
     When the server stops, it's unregistered:
 
-    >>> stop_server(server)
+    >>> zc.zkzopeserver.stop_for_testing(server)
     >>> zk.print_tree('/fooservice/providers')
     /providers
 



More information about the checkins mailing list