[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