[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