[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