Commit 49400d40 authored by Kazuhiko Shiozaki's avatar Kazuhiko Shiozaki

we don't use ClockServer. we use TimerService instead.

parent 07a136ec
master afs afs-manifest afs_fix allow_login_change allow_login_change_differentiate_id_and_login allow_login_change_wip arnau arnau-kns arnau-kns-without-property-mapping auto_extend_select_list autoflake backup_erp5_workflow bk_erp5ish_actions_tool bk_sqlcatalog catalog_fulltext catalog_fulltext_old cedric cedriclen-eos change_http_exchange_api cherry-pick-4a8e045d clean_up_upgrader cleanup_acquisition_base_category compact_title_no_reference couscous credential_update_action datetimefield douglas_forum dream_distributor dsn-phase3 eos-dev erp5-component erp5-data-notebook erp5-forum erp5-preference erp5-release erp5-slapos-upgrade erp5-vifib erp5-vifib-cleanup erp5_calendar erp5_catalog erp5_ci erp5_corporate_identity erp5_free_subscription erp5_payslip_migration erp5_recruiting erp5_web_forwarder erp5_web_templates erp5_workflow erp5testnode_max_timeout feat/no_getPrice_on_edit feat/no_useless_records_in_predicate_table feat/wendelin-py-data fix/allowing_in_restricted fix/clone_transformation fix/inventory_api_variation_category fix/login_validate_check_consistency for_testrunner_1 for_testrunner_2 formbox gabriel-fix-rounding-in-accounting-generation gabriel-fix-rounding-in-accounting-generation2 gadget-json-value improve_default_caching_policy_manager isDeletable item_tracking_graph_editor jerome-test jerome_graph_editor_renderjs jerome_user_preference_time_zone jm/form-action-guard joblib-activity kns kns-kr master_calendar_wip_patches master_calendar_wip_patches_extend_security master_no_guard_on_workflow_transition master_no_guard_on_workflow_transition_plus_calendar_wip_patchs nexedi-erp5-jp no_longer_simulated_state officejs officejs_clean portal_callables portal_solver_process_security_configuration reindex_calendar_after_change_calendar_exception reverse_logistics rewrite_test_21_AcquiredPortalType romain-fulltext scalability-rebase shop-box sms_more_than_140_characters strict_catalog taskdistribution-xmlrpc-binary testnode_software_link timezones tmp/getInstalledBusinessTemplate_never_installed_bt tristan tristan-merge tristan-performance view-aggregated-amounts vivekpab_erp5webrenderjs_layoutconfig vivekpab_jabberclient vivekpab_renderjs_interfaces wenjie wenjie_branch wip_erp5_event erp5.util-0.4.46 erp5.util-0.4.44 erp5.util-0.4.43 erp5.util-0.4.41 erp5.util-0.4.40 erp5.util-0.4.37
No related merge requests found
##############################################################################
#
# Copyright (c) 2008 Nexedi SARL and Contributors. All Rights Reserved.
# Vincent Pelletier <vincent@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 __init__(self, *args, **kw):
self.shutdown_method = kw.pop('shutdown_method')
OriginalClockServer.__init__(self, *args, **kw)
if self.shutdown_method is None:
self.log_info('ClockServer shutdown_method is not set in configuration'\
'file. Unclean shutdown can happen.', type='warning')
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
if self.shutdown_method is not None:
# XXX: should use a float for time representation
method = '%s?phase:int=%i&time_in_phase:float=%f' % \
(self.shutdown_method, _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.
##############################################################################
#
# 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')
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
shutdown_method /Control_Panel/timer_service/process_shutdown
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.
<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="shutdown_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 the following arguments:
phase (number)
Shutdown phase number.
See Lifetime.py .
time_in_phase (number)
Time spent in current phase, in seconds.
Given method path must not contain any argument. 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>
##############################################################################
#
# 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.shutdown_method = section.shutdown_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,
shutdown_method=self.shutdown_method)
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment