[Erp5-report] r15959 - /erp5/trunk/utils/listman/
nobody at svn.erp5.org
nobody at svn.erp5.org
Thu Aug 30 16:13:23 CEST 2007
Author: bartek
Date: Thu Aug 30 16:13:23 2007
New Revision: 15959
URL: http://svn.erp5.org?rev=15959&view=rev
Log:
mailing list membership manager - initial import
Added:
erp5/trunk/utils/listman/
erp5/trunk/utils/listman/ImplMailman.py
erp5/trunk/utils/listman/ImplTemplate.py
erp5/trunk/utils/listman/MAINTAINERS.txt
erp5/trunk/utils/listman/README
erp5/trunk/utils/listman/VERSION.txt
erp5/trunk/utils/listman/config.py
erp5/trunk/utils/listman/lib_listman.py
erp5/trunk/utils/listman/listman.conf
erp5/trunk/utils/listman/listman.py (with props)
erp5/trunk/utils/listman/listman.rc (with props)
erp5/trunk/utils/listman/logger.py
erp5/trunk/utils/listman/testListman.py (with props)
Added: erp5/trunk/utils/listman/ImplMailman.py
URL: http://svn.erp5.org/erp5/trunk/utils/listman/ImplMailman.py?rev=15959&view=auto
==============================================================================
--- erp5/trunk/utils/listman/ImplMailman.py (added)
+++ erp5/trunk/utils/listman/ImplMailman.py Thu Aug 30 16:13:23 2007
@@ -1,0 +1,127 @@
+#!/usr/bin/python
+
+import os
+import re
+import tempfile
+
+import ImplTemplate
+from config import config
+from lib_listman import *
+
+bin_path = config.get('List', 'bin_path')
+
+# regexp for analyzing result of sync_members command
+full_pattern = '(Added|Removed|Subscribed)[^<]+<([^>]+)>.*'
+full_rx = re.compile(full_pattern)
+plain_pattern = '(Added|Removed|Subscribed)[ :]+(.*)'
+plain_rx = re.compile(plain_pattern)
+
+def _procCmdResult(result):
+ """
+ An iterator for analyzing result of sync_members command
+ Yields tuples (action, address)
+ """
+ result_list = result.split('\n')
+ for line in result_list:
+ match_object = full_rx.match(line)
+ if match_object is not None:
+ yield match_object.groups()
+ else:
+ match_object = plain_rx.match(line)
+ if match_object is not None:
+ yield match_object.groups()
+
+
+class ListManager(ImplTemplate.ListManager):
+
+ ######## helpers
+
+ def checkHasList(func):
+ """
+ Decorator for checking of the list of a given id exists
+ Every function which pertains to a certain list receives
+ list_id as the first arg
+ """
+ def wrappedFunc(self, list_id=None, *args, **kwargs):
+ if not list_id:
+ # we decorate only funcs which require list_id,
+ # but we don't want wrappedFunc to raise exception
+ raise Exception(func.__name__ + ' requires list_id argument')
+ if not self.hasList(list_id):
+ raise NoList(list_id)
+ return func(self, list_id, *args, **kwargs)
+ return wrappedFunc
+
+ ######## MailMan implementation API
+
+ def getListList(self):
+ return self._getList('list_lists', '-b')
+
+ def hasList(self, list_id):
+ return list_id in self.getListList()
+
+ @checkHasList
+ def getMemberList(self, list_id):
+ return self._getList('list_members', list_id)
+
+ @checkHasList
+ def setMemberList(self, list_id, member_list):
+ # write to a file so that we can sync using mailman's script
+ temp_file_handle, temp_file_name = tempfile.mkstemp()
+ temp_file = open(temp_file_name, 'w')
+ temp_file.write('\n'.join(set(member_list))) # remove duplicates
+ temp_file.close()
+ result_dict = dict(added=[], removed=[])
+ cmd_result = self._execCommand('sync_members', '-f', temp_file_name, list_id)
+ for result in _procCmdResult(cmd_result):
+ # add user ids to appropriate lists in the result dict (added/removed)
+ result_dict[result[0].lower()].append(result[1])
+ os.unlink(temp_file_name)
+ return result_dict
+
+ @checkHasList
+ def hasMember(self, list_id, member_id):
+ return member_id in self.getMemberList(list_id)
+
+ @checkHasList
+ def addMemberList(self, list_id, member_list):
+ # write to a file so that we can add using mailman's script
+ temp_file_handle, temp_file_name = tempfile.mkstemp()
+ temp_file = open(temp_file_name, 'w')
+ temp_file.write('\n'.join(set(member_list))) # remove duplicates
+ temp_file.close()
+ cmd_result = self._execCommand('add_members', '-r', temp_file_name, list_id)
+ os.unlink(temp_file_name)
+ return [id[1] for id in _procCmdResult(cmd_result) if id[0] == 'Subscribed']
+
+ @checkHasList
+ def getNewMemberList(self, list_id, member_list):
+ current_list = set(self.getMemberList(list_id))
+ member_list = set(member_list)
+ return list(current_list - member_list)
+
+ ######## private implementation methods
+
+ def _getList(self, cmd, *args):
+ """
+ Executes command and returns output as list
+ """
+ raw_list = self._execCommand(cmd, *args)
+ fmt_list = raw_list.split()
+ return fmt_list
+
+ def _execCommand(self, cmd, *args):
+ cmd = os.path.join(bin_path, cmd)
+ # check if exists and is executable
+ if not os.path.exists(cmd):
+ raise Misconfiguration(cmd + ' does not exists')
+ if not os.access(cmd, os.X_OK):
+ raise Misconfiguration(cmd + ' is not executable')
+ cmd_line = cmd + ' ' + ' '.join(args)
+ print cmd_line
+ stdin, stdout = os.popen4(cmd_line)
+ output = stdout.read()
+ stdin.close()
+ stdout.close()
+ return output
+
Added: erp5/trunk/utils/listman/ImplTemplate.py
URL: http://svn.erp5.org/erp5/trunk/utils/listman/ImplTemplate.py?rev=15959&view=auto
==============================================================================
--- erp5/trunk/utils/listman/ImplTemplate.py (added)
+++ erp5/trunk/utils/listman/ImplTemplate.py Thu Aug 30 16:13:23 2007
@@ -1,0 +1,77 @@
+#!/usr/bin/python
+
+
+class ListManager(object):
+ """
+ Template for implementations of list managers
+ Contains API which returns NotImplementedError
+ Real implementations should inherit from this class.
+ """
+
+ def ping(self):
+ """
+ The only method implemented here.
+ It used to test if the current implementation works.
+ Now it doesn't test anything because this is supposed to be
+ the right way to do it.
+ """
+ return 1
+
+ def getListList(self):
+ """
+ Get a list of existing mailing lists.
+ Returns list
+ """
+ raise NotImplementedError
+
+ def hasList(self, list_id):
+ """
+ Check if the server has this list
+ Returns bool
+ """
+ raise NotImplementedError
+
+ def getMemberList(self, list_id):
+ """
+ Get a list of members of a given mailing list
+ """
+ raise NotImplementedError
+
+ def setMemberList(self, list_id, member_list):
+ """
+ Set a list of members for a given mailing list
+ remove those who are not on the list
+ Returns a dict containing two lists:
+ - 'added': members added to the list
+ - 'removed': members removed from the list
+ """
+ raise NotImplementedError
+
+ def hasMember(self, list_id, member_id):
+ """
+ Check if this list has this member as an active subscriber
+ Returns bool
+ """
+ raise NotImplementedError
+
+ def addMemberList(self, list_id, member_list):
+ """
+ Add members to the list (skipping those who are already there)
+ Returns list of members added to the list.
+ """
+ raise NotImplementedError
+
+ def getNewMemberList(self, list_id, member_list):
+ """
+ Compare the member_list to the list of current subscribers
+ (to see who subscribed directly)
+ Return a list of those who are subscribed but not on the member_list.
+ """
+ raise NotImplementedError
+
+ def thisIsNotImplemented(self):
+ """
+ Do not implement this one, it is only for testing.
+ """
+ raise NotImplementedError
+
Added: erp5/trunk/utils/listman/MAINTAINERS.txt
URL: http://svn.erp5.org/erp5/trunk/utils/listman/MAINTAINERS.txt?rev=15959&view=auto
==============================================================================
--- erp5/trunk/utils/listman/MAINTAINERS.txt (added)
+++ erp5/trunk/utils/listman/MAINTAINERS.txt Thu Aug 30 16:13:23 2007
@@ -1,0 +1,2 @@
+bartek
+jp
Added: erp5/trunk/utils/listman/README
URL: http://svn.erp5.org/erp5/trunk/utils/listman/README?rev=15959&view=auto
==============================================================================
--- erp5/trunk/utils/listman/README (added)
+++ erp5/trunk/utils/listman/README Thu Aug 30 16:13:23 2007
@@ -1,0 +1,33 @@
+DESCRIPTION
+
+This is a simple tool for remote, programmatic management of mailing
+list membership. It is generic, plus it contains an implementation for
+Mailman listserver.
+
+The API is defined and documented in ImplTemplate.py.
+
+Specific implementations should be writted in modules named
+"Impl[Server]" (like "ImplMailman") and contain ListManager class,
+inheriting from ImplTemplate.ListManager class and implementing all API
+methods from template class.
+
+Configuration file listman.conf contains an entry which says which
+implementation should be imported.
+
+The protocol (similar to oood's) is defined and described in
+lib_listman.py.
+
+The test suite testListman.py contains examples of everything.
+
+The server has no security or access control whatsoever, so can be used only
+locally or on very trusted networks with ip-based control. Mailman
+implementation relies on any system user being allowed to run mailman
+admin scripts, which in default configuration is true.
+
+VERSION:
+pre-alpha
+
+AUTHOR:
+Bartek Górny, ERP5 Polska sp. z o.o.
+
+# vim: tw=72
Added: erp5/trunk/utils/listman/VERSION.txt
URL: http://svn.erp5.org/erp5/trunk/utils/listman/VERSION.txt?rev=15959&view=auto
==============================================================================
--- erp5/trunk/utils/listman/VERSION.txt (added)
+++ erp5/trunk/utils/listman/VERSION.txt Thu Aug 30 16:13:23 2007
@@ -1,0 +1,1 @@
+0.1.0-1
Added: erp5/trunk/utils/listman/config.py
URL: http://svn.erp5.org/erp5/trunk/utils/listman/config.py?rev=15959&view=auto
==============================================================================
--- erp5/trunk/utils/listman/config.py (added)
+++ erp5/trunk/utils/listman/config.py Thu Aug 30 16:13:23 2007
@@ -1,0 +1,6 @@
+#!/usr/bin/python
+
+from ConfigParser import ConfigParser
+
+config = ConfigParser()
+config.read(['/etc/listman/listman.conf', 'listman.conf'])
Added: erp5/trunk/utils/listman/lib_listman.py
URL: http://svn.erp5.org/erp5/trunk/utils/listman/lib_listman.py?rev=15959&view=auto
==============================================================================
--- erp5/trunk/utils/listman/lib_listman.py (added)
+++ erp5/trunk/utils/listman/lib_listman.py Thu Aug 30 16:13:23 2007
@@ -1,0 +1,105 @@
+#!/usr/bin/python
+
+import types
+
+class InvalidStatusCode(Exception): pass
+class NoList(Exception): pass
+class Misconfiguration(Exception): pass
+
+OK = 200
+BAD_REQUEST = 401
+NO_LIST = 402
+NOT_PROVIDED = 403
+NOT_IMPLEMENTED = 404
+NO_REPONSE = 501
+MISCONFIG = 502
+UNKNOWN = 504
+
+code_map = {
+ 200: 'OK',
+ 401: 'bad request',
+ 402: 'the list does not exist',
+ 403: 'requested method is not provided',
+ 404: 'requested method is not implemented',
+ 501: 'the list server is not responding', # this doesn't seem to ever happen ;)
+ 502: 'server misconfiguration',
+ 504: 'unknown error'
+ }
+
+
+def code2message(code):
+ """
+ Returns status message of the given status code
+ """
+ return code_map.get(int(code), 'unknown code')
+
+
+class Response(object):
+ """
+ This class represents a response.
+ It is used to produce a response.
+ Response is given as a tuple of three items:
+ - status code
+ - response data
+ - message
+ Response data is a dictionary, which either has multiple keys
+ or only one key 'response_data'
+ """
+
+ code = 0
+
+ def __init__(self, code, data={}, message=''):
+ self.code = code
+ if not message:
+ message = code2message(code)
+ # data is always a dict - if return value is not a dict, we
+ # make a dict {'response_data': data}
+ if type(data) == types.DictType:
+ self.data = data
+ else:
+ self.data = dict(response_data=data)
+ self.message = message
+
+ def asResponse(self):
+ """
+ Produces an appropriately formatted response.
+ """
+ return (self.code, self.data, self.message)
+
+ ############ Helper methods - something like currying
+ #
+ # XXX could be auto-generated, but like that it is more readable
+
+ @classmethod
+ def OK(cls, data={}, message=''):
+ return cls(200, data, message).asResponse()
+
+ @classmethod
+ def BAD_REQUEST(cls, data={}, message=''):
+ return cls(401, data, message).asResponse()
+
+ @classmethod
+ def NO_LIST(cls, data={}, message=''):
+ return cls(402, data, message).asResponse()
+
+ @classmethod
+ def NOT_PROVIDED(cls, data={}, message=''):
+ return cls(403, data, message).asResponse()
+
+ @classmethod
+ def NOT_IMPLEMENTED(cls, data={}, message=''):
+ return cls(404, data, message).asResponse()
+
+ @classmethod
+ def NO_REPONSE(cls, data={}, message=''):
+ return cls(501, data, message).asResponse()
+
+ @classmethod
+ def MISCONFIG(cls, data={}, message=''):
+ return cls(502, data, message).asResponse()
+
+ @classmethod
+ def UNKNOWN(cls, data={}, message=''):
+ return cls(504, data, message).asResponse()
+
+
Added: erp5/trunk/utils/listman/listman.conf
URL: http://svn.erp5.org/erp5/trunk/utils/listman/listman.conf?rev=15959&view=auto
==============================================================================
--- erp5/trunk/utils/listman/listman.conf (added)
+++ erp5/trunk/utils/listman/listman.conf Thu Aug 30 16:13:23 2007
@@ -1,0 +1,16 @@
+
+[System]
+log_file = /var/log/listman/listman.log
+log_level = DEBUG
+
+[Server]
+server_host = 127.0.0.1
+server_port = 8000
+listman_home = /usr/share/listman
+run_dir = /var/run/listman
+
+
+
+[List]
+listserver = Mailman
+bin_path = /usr/sbin
Added: erp5/trunk/utils/listman/listman.py
URL: http://svn.erp5.org/erp5/trunk/utils/listman/listman.py?rev=15959&view=auto
==============================================================================
--- erp5/trunk/utils/listman/listman.py (added)
+++ erp5/trunk/utils/listman/listman.py Thu Aug 30 16:13:23 2007
@@ -1,0 +1,230 @@
+#!/usr/bin/python
+
+import inspect
+import os
+import socket
+import sys
+import time
+from SimpleXMLRPCServer import SimpleXMLRPCServer
+
+from config import config
+from logger import logger, getTraceback
+from lib_listman import *
+import ImplTemplate
+
+# here we dynamically select which implementation to import
+# based on config
+impl = config.get('List', 'listserver')
+module_name = 'Impl%s' % impl
+Impl = __import__(module_name)
+
+
+class MySerw(SimpleXMLRPCServer):
+
+ def server_bind(self):
+ self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
+ self.socket.bind(self.server_address)
+
+
+class Dispatcher(object):
+ """
+ This class handles incoming requests and sends a response
+ formatted, with status code, message and data.
+ See lib_listman module for description of response formatting
+ and for status codes.
+ """
+
+ def __init__(self):
+ """
+ Initiate instance; create implementation object.
+ We also test if everything is implemented
+ """
+ self.listman = Impl.ListManager()
+
+ def _dispatch(self, funcname, a):
+ """
+ The main dispatching method
+ Tries to find the right method, execute it and return a formatted
+ response
+ Handles exceptions and returns a response with appropriate error code
+ and message
+ """
+ if funcname.startswith('_'):
+ msg = 'Even if %s exists, it is a private method' % funcname
+ return Response.BAD_REQUEST(message=msg)
+ try:
+ func = getattr(self.listman, funcname, None)
+ if func is None:
+ msg = '%s is not part of the API' % funcname
+ logger.warning(msg)
+ return Response.NOT_PROVIDED(message=msg)
+ data = func(*a)
+ return Response.OK(data=data)
+ except NotImplementedError:
+ msg = '%s is not implemented' % funcname
+ logger.warning(msg)
+ return Response.NOT_IMPLEMENTED(message=msg)
+ except NoList, e:
+ msg = 'list "%s" does not exist' % str(e)
+ return Response.NO_LIST(message=msg)
+ except Misconfiguration, e:
+ msg = str(e)
+ return Response.MISCONFIG(message=msg)
+ except Exception, e:
+ logger.logException(e)
+ data = dict(exception=e.__class__.__name__, msg=str(e), traceback=getTraceback())
+ return Response.UNKNOWN(data=data, message=str(e))
+
+
+def usage():
+ print "Usage: listproc.py [--fork|--stop|--status [--log=out_log_file]]"
+ print "--fork starts server in the background"
+ print "--log redirects stdout and stderr to out_log_file instead of /dev/null"
+ print "--stop stops server that has been forked before"
+ print "--status checks if the server is running in background"
+ sys.exit(0)
+
+
+def printAndLog(message):
+ logger.info(message)
+ print message
+
+
+def createServer():
+ server = MySerw((config.get('Server', 'server_host'), config.getint('Server', 'server_port')))
+ server.register_instance(Dispatcher())
+ return server
+
+
+class OutputLogger:
+ """
+ a replacement for sys.stdout and sys.stderr
+ """
+ def __init__(self, out_log_name):
+ self.out_log_name = out_log_name
+
+ def write(self, output):
+ file_handle = open(self.out_log_name, 'a')
+ file_handle.write(output)
+ file_handle.close()
+
+
+def forkServer(lock_file_path, out_log_name):
+ """
+ Detach a process from the controlling terminal and run it in the
+ background as a daemon.
+ """
+ try:
+ pid = os.fork()
+ except OSError, e:
+ raise Exception, "%s [%d]" % (e.strerror, e.errno)
+ if (pid == 0): # The first child.
+ os.setsid()
+ try:
+ pid = os.fork() # Fork a second child.
+ except OSError, e:
+ raise Exception, "%s [%d]" % (e.strerror, e.errno)
+ if (pid == 0): # The second child.
+ try:
+ os.chdir("/")
+ # if we fork, we redirect all output to given log file (default is /dev/null)
+ sys.stdin = open('/dev/null', 'r')
+ sys.stdout = sys.stderr = OutputLogger(out_log_name)
+ server = createServer()
+ # if successful, we do all the lock file manipulation
+ lock_file = open(lock_file_path, 'w')
+ server_pid = os.getpid()
+ printAndLog("Server is now running, pid %s" % server_pid)
+ lock_file.write(str(server_pid))
+ lock_file.close()
+ # serve now...
+ server.serve_forever()
+ except Exception, e:
+ logger.logException(e) # we have to log because we most likely silenced all outputs
+ raise
+ else:
+ os._exit(0) # Exit parent (the first child) of the second child.
+ return(0)
+
+
+def pingServer():
+ """
+ Ping the server, return message
+ """
+ import xmlrpclib
+ server_url = 'http://%s:%s' % (config.get('Server', 'server_host'), config.get('Server', 'server_port'))
+ sp = xmlrpclib.ServerProxy(server_url)
+ try:
+ response = sp.ping()
+ if response[0] == 200:
+ message = 'Listman server is up and responding'
+ else:
+ message = 'Listman server is up but returned status ' + str(response[0])
+ except Exception, e:
+ message = 'Failed - returned %s' % e
+ return message
+
+
+def stopServer(pid):
+ message = ''
+ try:
+ os.kill(server_pid, 15)
+ except OSError:
+ message = "No such pid - server must have crashed"
+ return message
+
+
+if __name__=='__main__':
+ import getopt
+ raw_options = getopt.getopt(sys.argv[1:], '', ('fork', 'stop', 'log=', 'help', 'status'))
+ options = dict(raw_options[0])
+ if options.has_key('--help'):
+ usage()
+ sys.exit(0)
+ fork = options.has_key('--fork')
+ out_log_name = options.get('--log', '/dev/null')
+ stop = options.has_key('--stop')
+ status = options.has_key('--status')
+ lock_file_path = os.path.join(config.get('Server', 'run_dir'), 'server_pid.lock')
+ ### STOP
+ if stop:
+ try:
+ server_pid = int(open(lock_file_path).read().strip())
+ except IOError:
+ printAndLog("No pid file, server is already down")
+ sys.exit(1)
+ printAndLog("Stopping server, killing pid %s" % server_pid)
+ message = stopServer(server_pid)
+ if message:
+ printAndLog(message)
+ sys.exit(1)
+ os.remove(lock_file_path)
+ sys.exit(0)
+ ### STATUS
+ if status:
+ if not os.path.exists(lock_file_path):
+ message = 'Listman server is down (pid file does not exist)'
+ else:
+ # ping server
+ message = pingServer()
+ printAndLog(message)
+ sys.exit(0)
+ ### START
+ if fork:
+ if os.path.exists(lock_file_path):
+ printAndLog('Server is already running - pid file exists')
+ sys.exit(1)
+ forkServer(lock_file_path, out_log_name)
+ # parent waits a little while and checks
+ time.sleep(3)
+ if os.path.exists(lock_file_path):
+ sys.exit(0)
+ else:
+ printAndLog('Server did not start - no pid file')
+ sys.exit(1)
+ else:
+ # run in the foreground
+ server = createServer()
+ printAndLog('Server created, beginning to serve...')
+ server.serve_forever()
+ usage()
Propchange: erp5/trunk/utils/listman/listman.py
------------------------------------------------------------------------------
svn:executable =
Added: erp5/trunk/utils/listman/listman.rc
URL: http://svn.erp5.org/erp5/trunk/utils/listman/listman.rc?rev=15959&view=auto
==============================================================================
--- erp5/trunk/utils/listman/listman.rc (added)
+++ erp5/trunk/utils/listman/listman.rc Thu Aug 30 16:13:23 2007
@@ -1,0 +1,97 @@
+#!/bin/sh
+##############################################################################
+#
+# Copyright (c) 2007 Nexedi SARL and Contributors. All Rights Reserved.
+# Bartek Gorny <bartek at erp5.pl>
+#
+# 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.
+#
+##############################################################################
+
+# Startup script for a listman server
+#
+# chkconfig: 2345 99 99
+# description: oood, the standalone server for converting various types of docs to/from OpenOffice format
+#
+
+# Source function library.
+. /etc/rc.d/init.d/functions
+
+NAME="listman"
+USER=listman
+LISTMAN_CONFIG_FILE=/etc/listman/listman.conf #change this if want to use another config file
+HOME=$(awk -F= ' /^listman_home/ { print $2 }' $LISTMAN_CONFIG_FILE )
+RUNDIR=$(awk -F= ' /^run_dir/ { print $2 }' $LISTMAN_CONFIG_FILE )
+
+PYTHON=python
+
+start() {
+ gprintf "Starting %s: " "$NAME"
+ # Start the server in the background
+ su --login $USER --command="$PYTHON $HOME/listman.py --fork" && echo_success || echo_failure
+ echo
+}
+
+
+stop() {
+ gprintf "Stopping %s daemon: " "$NAME"
+ su --login $USER --command="$PYTHON $HOME/listman.py --stop" && echo_success || echo_failure
+ echo
+}
+
+
+status() {
+ echo
+ su --login $USER --command="$PYTHON $HOME/listman.py --status"
+ echo
+}
+
+
+case "$1" in
+ start)
+ touch /var/lock/subsys/$NAME
+ start
+ ;;
+
+ stop)
+ stop
+ rm -f /var/lock/subsys/$NAME
+ rm -f $LOCKFILE #sometimes is not removed by runserv.py
+
+ ;;
+
+ restart)
+ stop
+ start
+ ;;
+
+ status)
+ status
+ ;;
+ *)
+
+ gprintf "Usage: %s\n" "$0 {start|stop|restart|status}"
+ exit 1
+
+esac
+
+exit
Propchange: erp5/trunk/utils/listman/listman.rc
------------------------------------------------------------------------------
svn:executable =
Added: erp5/trunk/utils/listman/logger.py
URL: http://svn.erp5.org/erp5/trunk/utils/listman/logger.py?rev=15959&view=auto
==============================================================================
--- erp5/trunk/utils/listman/logger.py (added)
+++ erp5/trunk/utils/listman/logger.py Thu Aug 30 16:13:23 2007
@@ -1,0 +1,48 @@
+############################################################################
+# Copyright (c) 2007 Adam Mikuta <adamm at erp5.pl>
+#
+# 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+#
+############################################################################
+
+import logging
+import os
+import sys
+import traceback
+
+from config import config
+
+filename = config.get('System', 'log_file')
+
+logging.basicConfig(level=eval('logging.'+config.get('System', 'log_level')),
+ format='%(asctime)s %(levelname)-8s %(message)s',
+ datefmt='%a, %d %b %Y %H:%M:%S',
+ filename=filename,
+ filemode='a')
+
+
+def logException(e):
+ logging.error(str(e))
+ logging.error(getTraceback())
+
+
+def getTraceback():
+ tb = sys.exc_info()[2]
+ return ''.join(traceback.format_tb(tb))
+
+logger = logging
+
+logger.logException = logException
+
Added: erp5/trunk/utils/listman/testListman.py
URL: http://svn.erp5.org/erp5/trunk/utils/listman/testListman.py?rev=15959&view=auto
==============================================================================
--- erp5/trunk/utils/listman/testListman.py (added)
+++ erp5/trunk/utils/listman/testListman.py Thu Aug 30 16:13:23 2007
@@ -1,0 +1,161 @@
+#!/usr/bin/python
+
+import sys
+import unittest
+import xmlrpclib
+
+from config import config
+
+class Response(object):
+
+ def __init__(self, resp):
+ self.code = resp[0]
+ self.message = resp[2]
+ self.__dict__.update(resp[1])
+
+ def isOk(self):
+ return self.code == 200
+
+ def hasCode(self, code):
+ return self.code == code
+
+def stdoutput(message):
+ print ' Response message:', message
+
+class TestListMembershipManagement(unittest.TestCase):
+
+ def _setInitialMemberList(self):
+ # set member list
+ member_list = ['aaa at a.com', 'bbb at a.com']
+ resp = Response(sp.setMemberList('one', member_list))
+ self.failUnless(resp.isOk())
+
+ def test_01_ListInfo(self):
+ """
+ Ask the listserver for a list of available mailing lists
+ Make sure 'one' is on it
+ """
+ # get a list of lists
+ resp = Response(sp.getListList())
+ self.failUnless(resp.isOk())
+ self.failUnless('one' in resp.response_data)
+ # see if a list exists
+ resp = Response(sp.hasList('one'))
+ self.failUnless(resp.isOk())
+ self.failUnless(resp.response_data)
+ resp = Response(sp.hasList('no_such_list'))
+ self.failUnless(resp.isOk())
+ self.failIf(resp.response_data)
+
+ def test_02_setMemberList(self):
+ """
+ Set member list
+ Make sure the listserver returns member list equal to what we've set
+ """
+ # set member list
+ member_list = ['aaa at a.com', 'bbb at a.com']
+ resp = Response(sp.setMemberList('one', member_list))
+ self.failUnless(resp.isOk())
+ # get it back
+ resp = Response(sp.getMemberList('one'))
+ self.failUnless(resp.isOk())
+ self.assertEqual(member_list, resp.response_data)
+
+ def test_03_memberInfo(self):
+ """
+ Set member list
+ Test if the listserver gives correct answers when we ask if someone
+ is a member
+ """
+ self._setInitialMemberList()
+ # check is member
+ resp = Response(sp.hasMember('one', 'aaa at a.com'))
+ self.failUnless(resp.isOk())
+ self.failUnless(resp.response_data)
+ # check is not member
+ resp = Response(sp.hasMember('one', 'yyy at a.com'))
+ self.failUnless(resp.isOk())
+ self.failIf(resp.response_data)
+
+ def test_04_changeMemberList(self):
+ """
+ Change member list to another value
+ Test if the server returns correct lists of added and removed members
+ """
+ self._setInitialMemberList()
+ # set another member list
+ member_list = ['bbb at a.com', 'ccc at a.com']
+ resp = Response(sp.setMemberList('one', member_list))
+ self.failUnless(resp.isOk())
+ # make sure we got a list of added and removed members
+ self.assertEqual(resp.removed, ['aaa at a.com'])
+ self.assertEqual(resp.added, ['ccc at a.com'])
+
+ def test_05_addMemberList(self):
+ """
+ Add some members
+ Test if the server added those who were not already members
+ """
+ self._setInitialMemberList()
+ new_member_list = ['bbb at a.com', 'ddd at a.com']
+ resp = Response(sp.addMemberList('one', new_member_list))
+ self.failUnless(resp.isOk())
+ self.assertEqual(resp.response_data, ['ddd at a.com'])
+
+ def test_06_getNewMemberList(self):
+ """
+ Give the server an old membership list
+ check who is new (who subscribed in the meantime)
+ """
+ self._setInitialMemberList()
+ old_member_list = ['aaa at a.com', 'xxx at a.com']
+ resp = Response(sp.getNewMemberList('one', old_member_list))
+ self.failUnless(resp.isOk())
+ self.assertEqual(resp.response_data, ['bbb at a.com'])
+
+ def test_07_protocol(self):
+ """
+ Give some bad requests
+ Test if the response code is correct
+ """
+ print
+ # illegal function name
+ resp = Response(sp._funcWithUnderscore())
+ stdoutput(resp.message)
+ self.failUnless(resp.hasCode(401))
+ # a call for non existent list
+ resp = Response(sp.getMemberList('no_such_list'))
+ stdoutput(resp.message)
+ self.failUnless(resp.hasCode(402))
+ # function not provided in the API
+ resp = Response(sp.noSuchMethod())
+ stdoutput(resp.message)
+ self.failUnless(resp.hasCode(403))
+ # function not implemented
+ resp = Response(sp.thisIsNotImplemented())
+ stdoutput(resp.message)
+ self.failUnless(resp.hasCode(404))
+ # other error
+ resp = Response(sp.getMemberList()) # not enough args
+ stdoutput(resp.message)
+ self.failUnless(resp.hasCode(504))
+ resp = Response(sp.hasMember('one')) # not enough args (different place)
+ stdoutput(resp.message)
+ self.failUnless(resp.hasCode(504))
+ # how to test server misconfiguration...?
+
+
+if __name__=='__main__':
+ server_url = 'http://%s:%s' % (config.get('Server', 'server_host'), config.get('Server', 'server_port'))
+ sp = xmlrpclib.ServerProxy(server_url)
+ r = Response(sp.hasList('one'))
+ if not r.response_data:
+ print '======== CAN NOT PROCEED ========'
+ print 'create a list with id "one"'
+ print 'in Mailman use "/usr/sbin/newlist" for that'
+ sys.exit(0)
+ tests=(TestListMembershipManagement,)
+ for t in tests:
+ suite = unittest.makeSuite(t)
+ unittest.TextTestRunner(verbosity=2).run(suite)
+
Propchange: erp5/trunk/utils/listman/testListman.py
------------------------------------------------------------------------------
svn:executable =
More information about the Erp5-report
mailing list