[Erp5-report] r22385 - /erp5/trunk/products/ClockServer/
nobody at svn.erp5.org
nobody at svn.erp5.org
Wed Jul 9 15:50:43 CEST 2008
Author: vincent
Date: Wed Jul 9 15:50:42 2008
New Revision: 22385
URL: http://svn.erp5.org?rev=22385&view=rev
Log:
Initial checkin of modified ClockServer. See README to know what comes from Zope trunk, what has been altered and what is localy created.
Added:
erp5/trunk/products/ClockServer/
erp5/trunk/products/ClockServer/ClockServer.py
erp5/trunk/products/ClockServer/OriginalClockServer.py
erp5/trunk/products/ClockServer/README
erp5/trunk/products/ClockServer/__init__.py
erp5/trunk/products/ClockServer/component.xml
erp5/trunk/products/ClockServer/datatypes.py
Added: erp5/trunk/products/ClockServer/ClockServer.py
URL: http://svn.erp5.org/erp5/trunk/products/ClockServer/ClockServer.py?rev=22385&view=auto
==============================================================================
--- erp5/trunk/products/ClockServer/ClockServer.py (added)
+++ erp5/trunk/products/ClockServer/ClockServer.py Wed Jul 9 15:50:42 2008
@@ -1,0 +1,103 @@
+##############################################################################
+#
+# Copyright (c) 2008 Nexedi SARL and Contributors. All Rights Reserved.
+# Vincent Pelletier <vincent at nexedi.com>
+#
+# WARNING: This program as such is intended to be used by professional
+# programmers who take the whole responsability of assessing all potential
+# consequences resulting from its eventual inadequacies and bugs
+# End users who are looking for a ready-to-use solution with commercial
+# garantees and support are strongly adviced to contract a Free Software
+# Service Company
+#
+# This program is Free Software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+#
+##############################################################################
+
+from OriginalClockServer import ClockServer as OriginalClockServer
+from OriginalClockServer import DummyChannel
+from ZServer.medusa.http_server import http_request
+from ZServer.HTTPResponse import make_response
+from ZPublisher.HTTPRequest import HTTPRequest
+import StringIO
+import thread
+
+wait_for_close_lock = thread.allocate_lock()
+
+class ClockServer(OriginalClockServer):
+ running = True
+
+ def readable(self):
+ """
+ Avoid starting a new tic if shutdown started.
+ """
+ if self.running:
+ OriginalClockServer.readable(self)
+ return False
+
+ def clean_shutdown_control(self, _shutdown_phase, time_in_this_phase):
+ """
+ Inform invoked method that a shutdown is in progress.
+
+ Here we:
+ - Prevent regular tics from being sent. This does not prevent
+ already-issued tics from running.
+ - Issue a special tic, ran asynchronously from regular tics and
+ asynchronously from this thread.
+ - Wait for that special tic to return, so that we know all clean
+ shutdown handlers have completely run.
+ - Return control to caller.
+
+ To wait for shutdown handler to return, it has been chosen to use a
+ semaphore scheme. It has the following drawbacks:
+ - It is intrusive: we need to hook foreign classes, since it's not
+ the way things happen with regular zope data exchange.
+ - We can't get what the shutdown handler returned (http return code,
+ page content, ...) so we will never take Lifetime's veto. So shutdown
+ handler must block until shutdown is complete, which is not how
+ clean_shutdown_control is supposed to work. Note though that it is a
+ design weakness in clean_shutdown_control, since some shutdown
+ handlers might not have finshed their job at the time process gets
+ closed.
+ """
+ self.running = False
+ separator = '?' in self.method and '&' or '?'
+ # XXX: should use a float for time representation
+ # TODO: allow user to specify a separate shutdown method instead of
+ # reusing regular one.
+ method = '%s%sshutdown:int=1&phase:int=%i&time_in_phase:int=%i' % \
+ (self.method, separator, _shutdown_phase, time_in_this_phase)
+
+ stdin = StringIO.StringIO()
+ request_string = 'GET %s HTTP/1.0' % (method, )
+ request = http_request(DummyChannel(self), request_string, 'GET', method,
+ '1.0', self.headers)
+ environment = self.get_env(request)
+ response = make_response(request, environment)
+ # Hook response._finish to get a notification when request is over.
+ def _finish():
+ response.__class__._finish(response)
+ wait_for_close_lock.release()
+ response._finish = _finish
+ # (end of hook)
+ zope_request = HTTPRequest(stdin, environment, response)
+ wait_for_close_lock.acquire()
+ self.zhandler('Zope2', zope_request, response)
+ self.log_info('ClockServer: Waiting for shutdown handler.')
+ wait_for_close_lock.acquire()
+ self.log_info('ClockServer: Going on.')
+ wait_for_close_lock.release()
+ return 0 # TODO: detect an error to allow taking the veto.
+
Added: erp5/trunk/products/ClockServer/OriginalClockServer.py
URL: http://svn.erp5.org/erp5/trunk/products/ClockServer/OriginalClockServer.py?rev=22385&view=auto
==============================================================================
--- erp5/trunk/products/ClockServer/OriginalClockServer.py (added)
+++ erp5/trunk/products/ClockServer/OriginalClockServer.py Wed Jul 9 15:50:42 2008
@@ -1,0 +1,162 @@
+##############################################################################
+#
+# Copyright (c) 2005 Chris McDonough. All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE
+#
+##############################################################################
+
+""" Zope clock server. Generate a faux HTTP request on a regular basis
+by coopting the asyncore API. """
+
+import posixpath
+import os
+import socket
+import time
+import StringIO
+import asyncore
+
+from ZServer.medusa.http_server import http_request
+from ZServer.medusa.default_handler import unquote
+from ZServer.PubCore import handle
+from ZServer.HTTPResponse import make_response
+from ZPublisher.HTTPRequest import HTTPRequest
+
+def timeslice(period, when=None, t=time.time):
+ if when is None:
+ when = t()
+ return when - (when % period)
+
+class LogHelper:
+ def __init__(self, logger):
+ self.logger = logger
+
+ def log(self, ip, msg, **kw):
+ self.logger.log(ip + ' ' + msg)
+
+class DummyChannel:
+ # we need this minimal do-almost-nothing channel class to appease medusa
+ addr = ['127.0.0.1']
+ closed = 1
+
+ def __init__(self, server):
+ self.server = server
+
+ def push_with_producer(self):
+ pass
+
+ def close_when_done(self):
+ pass
+
+class ClockServer(asyncore.dispatcher):
+ # prototype request environment
+ _ENV = dict(REQUEST_METHOD = 'GET',
+ SERVER_PORT = 'Clock',
+ SERVER_NAME = 'Zope Clock Server',
+ SERVER_SOFTWARE = 'Zope',
+ SERVER_PROTOCOL = 'HTTP/1.0',
+ SCRIPT_NAME = '',
+ GATEWAY_INTERFACE='CGI/1.1',
+ REMOTE_ADDR = '0')
+
+ # required by ZServer
+ SERVER_IDENT = 'Zope Clock'
+
+ def __init__ (self, method, period=60, user=None, password=None,
+ host=None, logger=None, handler=None):
+ self.period = period
+ self.method = method
+
+ self.last_slice = timeslice(period)
+
+ h = self.headers = []
+ h.append('User-Agent: Zope Clock Server Client')
+ h.append('Accept: text/html,text/plain')
+ if not host:
+ host = socket.gethostname()
+ h.append('Host: %s' % host)
+ auth = False
+ if user and password:
+ encoded = ('%s:%s' % (user, password)).encode('base64')
+ h.append('Authorization: Basic %s' % encoded)
+ auth = True
+
+ asyncore.dispatcher.__init__(self)
+ self.create_socket(socket.AF_INET, socket.SOCK_STREAM)
+ self.logger = LogHelper(logger)
+ self.log_info('Clock server for "%s" started (user: %s, period: %s)'
+ % (method, auth and user or 'Anonymous', self.period))
+ if handler is None:
+ # for unit testing
+ handler = handle
+ self.zhandler = handler
+
+ def get_requests_and_response(self):
+ out = StringIO.StringIO()
+ s_req = '%s %s HTTP/%s' % ('GET', self.method, '1.0')
+ req = http_request(DummyChannel(self), s_req, 'GET', self.method,
+ '1.0', self.headers)
+ env = self.get_env(req)
+ resp = make_response(req, env)
+ zreq = HTTPRequest(out, env, resp)
+ return req, zreq, resp
+
+ def get_env(self, req):
+ env = self._ENV.copy()
+ (path, params, query, fragment) = req.split_uri()
+ if params:
+ path = path + params # undo medusa bug
+ while path and path[0] == '/':
+ path = path[1:]
+ if '%' in path:
+ path = unquote(path)
+ if query:
+ # ZPublisher doesn't want the leading '?'
+ query = query[1:]
+ env['PATH_INFO']= '/' + path
+ env['PATH_TRANSLATED']= posixpath.normpath(
+ posixpath.join(os.getcwd(), env['PATH_INFO']))
+ if query:
+ env['QUERY_STRING'] = query
+ env['channel.creation_time']=time.time()
+ for header in req.header:
+ key,value = header.split(":",1)
+ key = key.upper()
+ value = value.strip()
+ key = 'HTTP_%s' % ("_".join(key.split( "-")))
+ if value:
+ env[key]=value
+ return env
+
+ def readable(self):
+ # generate a request at most once every self.period seconds
+ slice = timeslice(self.period)
+ if slice != self.last_slice:
+ # no need for threadsafety here, as we're only ever in one thread
+ self.last_slice = slice
+ req, zreq, resp = self.get_requests_and_response()
+ self.zhandler('Zope2', zreq, resp)
+ return False
+
+ def handle_read(self):
+ return True
+
+ def handle_write (self):
+ self.log_info('unexpected write event', 'warning')
+ return True
+
+ def writable(self):
+ return False
+
+ def handle_error (self): # don't close the socket on error
+ (file,fun,line), t, v, tbinfo = asyncore.compact_traceback()
+ self.log_info('Problem in Clock (%s:%s %s)' % (t, v, tbinfo),
+ 'error')
+
+
+
Added: erp5/trunk/products/ClockServer/README
URL: http://svn.erp5.org/erp5/trunk/products/ClockServer/README?rev=22385&view=auto
==============================================================================
--- erp5/trunk/products/ClockServer/README (added)
+++ erp5/trunk/products/ClockServer/README Wed Jul 9 15:50:42 2008
@@ -1,0 +1,44 @@
+This is an unnoficial Zope 2.8 backport of official Zope's ClockServer.
+
+Status of initial checkin compared to official version:
+ __init__.py
+ Locally created.
+ ClockServer.py
+ Locally created.
+ component.xml
+ Fix component prefix.
+ datatypes.py
+ Import ServerFactory (originaly locally defined).
+ Fix ClockServer class import.
+ OriginalClockServer.py
+ Unchanged original ClockServer.py.
+ README
+ Locally created.
+
+To enable it, add (and adapt) the following to your zope.conf:
+ %import Products.ClockServer
+ <clock-server>
+ # starts a clock which calls /foo/bar every 30 seconds
+ method /foo/bar
+ period 30
+ user admin
+ password 123
+ </clock-server>
+
+ERP5 users: You are strongly encouraged to kee TimerService (but to stop using
+timerserver) and use the following configuration:
+
+ method /Control_Panel/timer_service/process_timer?interval:int=5
+ period 5
+
+Note: Because ClockServer uses asyncore's "readable" method polling,
+configured frequency is only a maximum value. Minimum freqency depends on
+asyncore configuration (one wakeup every 30s on my machine). If there is
+activity on Zope's sockets, frequency will increase.
+
+This ClockServer is extended (see ClockServer.py) to propagate shutdown
+notification to configured method, by pasing it extra parameters.
+This allows method to put shutdown sequence on hold (but not interrupt it).
+Also, note that it must not be abused: it's both bad to make user wait, and
+there are some timeouts killing Zope if it takes too long to stop.
+
Added: erp5/trunk/products/ClockServer/__init__.py
URL: http://svn.erp5.org/erp5/trunk/products/ClockServer/__init__.py?rev=22385&view=auto
==============================================================================
(empty)
Added: erp5/trunk/products/ClockServer/component.xml
URL: http://svn.erp5.org/erp5/trunk/products/ClockServer/component.xml?rev=22385&view=auto
==============================================================================
--- erp5/trunk/products/ClockServer/component.xml (added)
+++ erp5/trunk/products/ClockServer/component.xml Wed Jul 9 15:50:42 2008
@@ -1,0 +1,39 @@
+<component prefix="Products.ClockServer.datatypes">
+ <sectiontype name="clock-server"
+ datatype=".ClockServerFactory"
+ implements="ZServer.server">
+ <key name="method" datatype="string">
+ <description>
+ The traversal path (from the Zope root) to an
+ executable Zope method (Python Script, external method, product
+ method, etc). The method must take no arguments. Ex: "/site/methodname"
+ </description>
+ </key>
+ <key name="period" datatype="integer" default="60">
+ <description>
+ The number of seconds between each clock "tick" (and
+ thus each call to the above "method"). The lowest number
+ providable here is typically 30 (this is the asyncore mainloop
+ "timeout" value). The default is 60. Ex: "30"
+ </description>
+ </key>
+ <key name="user" datatype="string">
+ <description>
+ A zope username. Ex: "admin"
+ </description>
+ </key>
+ <key name="password" datatype="string">
+ <description>
+ The password for the zope username provided above. Careful: this
+ is obviously not encrypted in the config file. Ex: "123"
+ </description>
+ </key>
+ <key name="host" datatype="string">
+ <description>
+ The hostname passed in via the "Host:" header in the
+ faux request. Could be useful if you have virtual host rules
+ set up inside Zope itself. Ex: "www.example.com"
+ </description>
+ </key>
+ </sectiontype>
+</component>
Added: erp5/trunk/products/ClockServer/datatypes.py
URL: http://svn.erp5.org/erp5/trunk/products/ClockServer/datatypes.py?rev=22385&view=auto
==============================================================================
--- erp5/trunk/products/ClockServer/datatypes.py (added)
+++ erp5/trunk/products/ClockServer/datatypes.py Wed Jul 9 15:50:42 2008
@@ -1,0 +1,32 @@
+##############################################################################
+#
+# Copyright (c) 2003 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+
+from ZServer.datatypes import ServerFactory
+
+class ClockServerFactory(ServerFactory):
+ def __init__(self, section):
+ ServerFactory.__init__(self)
+ self.method = section.method
+ self.period = section.period
+ self.user = section.user
+ self.password = section.password
+ self.hostheader = section.host
+ self.host = None # appease configuration machinery
+
+ def create(self):
+ from Products.ClockServer.ClockServer import ClockServer
+ from ZServer.AccessLogger import access_logger
+ return ClockServer(self.method, self.period, self.user,
+ self.password, self.hostheader, access_logger)
+
More information about the Erp5-report
mailing list