3 commits - tests/test-002-attendee.py tests/test-003-event.py wallace/__init__.py wallace/module_resources.py

Jeroen van Meeuwen vanmeeuwen at kolabsys.com
Thu May 24 18:22:52 CEST 2012


 tests/test-002-attendee.py  |   75 +++++++
 tests/test-003-event.py     |   27 +-
 wallace/__init__.py         |  439 +++++++++++++-------------------------------
 wallace/module_resources.py |  200 +++++++++++++++-----
 4 files changed, 378 insertions(+), 363 deletions(-)

New commits:
commit c116e801d7611d38f5c70f0d148de1616be5643e
Author: Jeroen van Meeuwen (Kolab Systems) <vanmeeuwen at kolabsys.com>
Date:   Thu May 24 17:20:48 2012 +0100

    - Use pools instead of the less subtle threading, as threading would
      allow the daemon to lock up under heavy (> 10k/s) load.

diff --git a/wallace/__init__.py b/wallace/__init__.py
index edd47a6..0d04c5b 100644
--- a/wallace/__init__.py
+++ b/wallace/__init__.py
@@ -18,13 +18,16 @@
 #
 
 import asyncore
+import binascii
 import grp
+import multiprocessing
 import os
 import pwd
 from smtpd import SMTPChannel
+import socket
+import struct
 import sys
 import tempfile
-import threading
 import time
 import traceback
 
@@ -34,6 +37,27 @@ from pykolab.translate import _
 log = pykolab.getLogger('pykolab.wallace')
 conf = pykolab.getConf()
 
+max_threads = 24
+
+def pickup_message(filepath, *args, **kw):
+    wallace_modules = args[0]
+    if kw.has_key('module'):
+
+        # Cause the previous modules to be skipped
+        wallace_modules = wallace_modules[(wallace_modules.index(kw['module'])+1):]
+
+        # Execute the module
+        if kw.has_key('stage'):
+            modules.execute(kw['module'], filepath, stage=kw['stage'])
+        else:
+            modules.execute(kw['module'], filepath)
+
+    for module in wallace_modules:
+        modules.execute(module, filepath)
+
+def worker_process(*args, **kw):
+    log.debug(_("Worker process %s initializing") % (multiprocessing.current_process().name), level=1)
+
 class WallaceDaemon(object):
     def __init__(self):
         daemon_group = conf.add_cli_parser_option_group(_("Daemon Options"))
@@ -95,122 +119,136 @@ class WallaceDaemon(object):
         import modules
         modules.__init__()
 
-    def process_message(self, peer, mailfrom, rcpttos, data):
-        """
-            We have retrieved the message.
+        self.modules = conf.get_list('wallace', 'modules')
+        if self.modules == None:
+            self.modules = ['resources']
+        elif not 'resources' in self.modules:
+            self.modules.append('resources')
 
-            - Dispatch to virus-scanning and anti-spam filtering?
-                Not for now. We use some sort of re-injection.
+    def do_wallace(self):
+        self.pool = multiprocessing.Pool(max_threads, worker_process, (), 1)
 
-            - Apply access policies;
-                - Maximum number of recipients,
-                - kolabAllowSMTPSender,
-                - kolabAllowSMTPRecipient,
-                - Rule-based matching against white- and/or blacklist
-                - ...
+        s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+        s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
 
-            - Accounting
+        bound = False
+        shutdown = False
+        while not bound:
+            try:
+                if shutdown:
+                    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+                    s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
 
-            - Data Loss Prevention
-        """
-        inheaders = 1
+                s.bind((conf.wallace_bind_address, conf.wallace_port))
+                bound = True
+            except Exception, e:
+                log.warning(
+                        _("Could not bind to socket on port %d on bind " + \
+                            "address %s") % (
+                                conf.wallace_port,
+                                conf.wallace_bind_address
+                            )
+                    )
 
-        (fp, filename) = tempfile.mkstemp(dir="/var/spool/pykolab/wallace/")
-        os.write(fp, data)
-        os.close(fp)
+                while not shutdown:
+                    try:
+                        s.shutdown(socket.SHUT_RDWR)
+                        shutdown = True
+                    except Exception, e:
+                        log.warning(_("Could not shut down socket"))
+                        time.sleep(1)
 
-        while threading.active_count() > 25:
-            log.debug(
-                    _("Number of threads currently running: %d") % (
-                            threading.active_count()
-                        ),
-                    level=8
-                )
-
-            time.sleep(1)
-
-        log.debug(
-                _("Continuing with %d threads currently running") % (
-                        threading.active_count()
-                    ),
-                level=8
-            )
+                s.close()
 
-        # TODO: Apply throttling
-        log.debug(_("Creating thread for message in %s") % (filename), level=8)
+                time.sleep(1)
 
-        thread = threading.Thread(target=self.thread_run, args=[ filename ])
-        thread.start()
+        s.listen(5)
 
-    def thread_run(self, filename, *args, **kw):
-        while threading.active_count() > 25:
-            log.debug(
-                    _("Number of threads currently running: %d") % (
-                            threading.active_count()
-                        ),
-                    level=8
-                )
+        # Mind you to include the trailing slash
+        pickup_path = '/var/spool/pykolab/wallace/'
+        for root, directory, files in os.walk(pickup_path):
+            for filename in files:
+                filepath = os.path.join(root, filename)
 
-            time.sleep(10)
+                if not root == pickup_path:
+                    module = os.path.dirname(root).replace(pickup_path, '')
 
-        log.debug(
-                _("Continuing with %d threads currently running") % (
-                        threading.active_count()
-                    ),
-                level=8
-            )
+                    # Compare uppercase status (specifically, DEFER) with
+                    # lowercase (plugin names).
+                    #
+                    # The messages in DEFER are supposed to be picked up by
+                    # another thread, whereas the messages in other directories
+                    # are pending being handled by their respective plugins.
+                    #
+                    # TODO: Handle messages in spool directories for which a
+                    # plugin had been enabled, but is not enabled any longer.
+                    #
 
-        log.debug(
-                _("Running thread %s for message file %s") % (
-                        threading.current_thread().name,
-                        filename
-                    ),
-                level=8
-            )
+                    if module.lower() == "defer":
+                        # Wallace was unable to deliver to re-injection smtpd.
+                        # Skip it, another thread is picking up the deferred
+                        # messages.
+                        continue
 
-        if kw.has_key('module'):
-            log.debug(
-                    _("This message was already in module %s, delegating " + \
-                        "specifically to that module") % (
-                            kw['module']
-                        ),
-                    level=8
-                )
-
-            if kw.has_key('stage'):
-                log.debug(
-                        _("It was also in a certain stage: %s, letting " + \
-                            "module %s know that") % (
-                                kw['stage'],
-                                kw['module']
-                            ),
-                        level=8
-                    )
+                    stage = root.replace(pickup_path, '').split('/')
+                    if len(stage) < 2:
+                        stage = None
+                    else:
+                        stage = stage[1]
 
-                log.debug(_("Executing module %s") % (kw['module']), level=8)
+                    if stage.lower() == "hold":
+                        continue
 
-                modules.execute(kw['module'], filename, stage=kw['stage'])
+                    # Do not handle messages in a defer state.
+                    if stage.lower() == "defer":
+                        continue
 
-                return
+                    self.pool.apply_async(pickup_message, (filepath, (self.modules), {'module': module, 'stage': stage}))
 
-            log.debug(_("Executing module %s") % (kw['module']), level=8)
-            modules.execute(kw['module'], filename, stage=kw['stage'])
+                    continue
 
-            return
+                self.pool.apply_async(pickup_message, (filepath, (self.modules)))
 
-        wallace_modules = conf.get_list('wallace', 'modules')
-        if wallace_modules == None:
-            wallace_modules = ['resources']
-        elif not 'resources' in wallace_modules:
-            wallace_modules.append('resources')
+        try:
+            while 1:
+                pair = s.accept()
+                log.info(_("Accepted connection"))
+                if not pair == None:
+                    connection, address = pair
+                    #print "Accepted connection from %r" % (address)
+                    channel = SMTPChannel(self, connection, address)
+                    asyncore.loop()
+        except Exception, errmsg:
+            traceback.print_exc()
+            s.shutdown(1)
+            s.close()
 
-        for module in wallace_modules:
-            log.debug(_("Executing module %s") % (module), level=8)
-            modules.execute(module, filename)
+    def process_message(self, peer, mailfrom, rcpttos, data):
+        """
+            We have retrieved the message. This should be as fast as possible,
+            and not ever block.
+        """
+        inheaders = 1
+
+        (fp, filename) = tempfile.mkstemp(dir="/var/spool/pykolab/wallace/")
+        os.write(fp, data)
+        os.close(fp)
+
+        self.pool.apply_async(pickup_message, (filename, (self.modules)))
+
+        return
+
+    def reload_config(self, *args, **kw):
+        pass
+
+    def remove_pid(self, *args, **kw):
+        if os.access(conf.pidfile, os.R_OK):
+            os.remove(conf.pidfile)
+        raise SystemExit
 
     def run(self):
         """
-            Run the SASL authentication daemon.
+            Run the Wallace daemon.
         """
 
         exitcode = 0
@@ -320,219 +358,6 @@ class WallaceDaemon(object):
 
         sys.exit(exitcode)
 
-    def pickup_defer(self):
-        wallace_modules = conf.get_list('wallace', 'modules')
-
-        if wallace_modules == None:
-            wallace_modules = []
-
-        base_path = '/var/spool/pykolab/wallace/'
-
-        while 1:
-            file_count = 0
-
-            log.debug(_("Picking up deferred messages for wallace"), level=8)
-
-            defer_path = os.path.join(base_path, 'DEFER')
-
-            if os.path.isdir(defer_path):
-                for root, directory, files in os.walk(defer_path):
-                    for filename in files:
-                        filepath = os.path.join(root, filename)
-
-                        file_count += 1
-
-                        for module in wallace_modules:
-                            modules.execute(module, filepath)
-
-                        time.sleep(1)
-
-            time.sleep(1)
-
-            for module in wallace_modules:
-                log.debug(
-                        _("Picking up deferred messages for module %s") % (
-                                module
-                            ),
-                        level=8
-                    )
-
-                module_defer_path = os.path.join(base_path, module, 'DEFER')
-
-                if os.path.isdir(module_defer_path):
-                    for root, directory, files in os.walk(module_defer_path):
-                        for filename in files:
-                            filepath = os.path.join(root, filename)
-
-                            file_count += 1
-
-                            modules.execute(module, filepath)
-
-                            time.sleep(1)
-
-                time.sleep(1)
-
-            # Sleep for 300 seconds before reprocessing the deferred queues.
-            # TODO: Consider using queue_run_delay from Postfix, which is where
-            # the default value of 300 seconds comes from.
-            log.debug(_("Sleeping for 300 seconds"), level=8)
-            time.sleep(300)
-
-    def do_wallace(self):
-        import binascii
-        import socket
-        import struct
-
-        #s = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
-        s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
-
-        ## TODO: The wallace socket path could be a setting.
-        #try:
-            #os.remove('/var/run/kolab/wallace')
-        #except:
-            ## TODO: Do the "could not remove, could not start dance"
-            #pass
-
-        bound = False
-        while not bound:
-            try:
-                s.bind((conf.wallace_bind_address, conf.wallace_port))
-                bound = True
-            except Exception, e:
-                log.warning(
-                        _("Could not bind to socket on port %d on bind " + \
-                            "address %s") % (
-                                conf.wallace_port,
-                                conf.wallace_bind_address
-                            )
-                    )
-
-                try:
-                    s.shutdown(1)
-                except Exception, e:
-                    log.warning(_("Could not shut down socket"))
-
-                s.close()
-
-                time.sleep(1)
-
-        s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
-        #os.chmod('/var/run/kolab/wallace', 0777)
-        #os.chgrp('/var/run/wallace/mux', 'kolab')
-        #os.chown('/var/run/wallace/mux', 'kolab')
-
-        s.listen(5)
-
-        # Mind you to include the trailing slash
-        pickup_path = '/var/spool/pykolab/wallace/'
-        for root, directory, files in os.walk(pickup_path):
-            for filename in files:
-                filepath = os.path.join(root, filename)
-
-                if not root == pickup_path:
-                    module = os.path.dirname(root).replace(pickup_path, '')
-
-                    # Compare uppercase status (specifically, DEFER) with
-                    # lowercase (plugin names).
-                    #
-                    # The messages in DEFER are supposed to be picked up by
-                    # another thread, whereas the messages in other directories
-                    # are pending being handled by their respective plugins.
-                    #
-                    # TODO: Handle messages in spool directories for which a
-                    # plugin had been enabled, but is not enabled any longer.
-                    #
-
-                    if module.lower() == "defer":
-                        # Wallace was unable to deliver to re-injection smtpd.
-                        # Skip it, another thread is picking up the deferred
-                        # messages.
-                        continue
-
-                    stage = root.replace(pickup_path, '').split('/')
-                    if len(stage) < 2:
-                        stage = None
-                    else:
-                        stage = stage[1]
-
-                    if stage.lower() == "hold":
-                        continue
-
-                    # Do not handle messages in a defer state.
-                    if stage.lower() == "defer":
-                        continue
-
-                    log.debug(
-                            _("Number of threads currently running: %d") % (
-                                    threading.active_count()
-                                ),
-                            level=8
-                        )
-
-                    thread = threading.Thread(
-                            target = self.thread_run,
-                            args = [ filepath ],
-                            kwargs = {
-                                    "module": '%s' % (module),
-                                    "stage": '%s' % (stage)
-                                }
-                        )
-
-                    thread.start()
-                    time.sleep(0.5)
-
-                    continue
-
-                log.debug(
-                        _("Picking up spooled email file %s") % (
-                                filepath
-                            ),
-                        level=8
-                    )
-
-                log.debug(
-                        _("Number of threads currently running: %d") % (
-                                threading.active_count()
-                            ),
-                        level=8
-                    )
-
-                thread = threading.Thread(
-                        target=self.thread_run,
-                        args=[ filepath ]
-                    )
-
-                thread.start()
-                time.sleep(0.5)
-
-        pid = os.fork()
-
-        if pid == 0:
-            self.pickup_defer()
-        else:
-
-            try:
-                while 1:
-                    pair = s.accept()
-                    log.info(_("Accepted connection"))
-                    if not pair == None:
-                        connection, address = pair
-                        #print "Accepted connection from %r" % (address)
-                        channel = SMTPChannel(self, connection, address)
-                        asyncore.loop()
-            except Exception, errmsg:
-                traceback.print_exc()
-                s.shutdown(1)
-                s.close()
-
-    def reload_config(self, *args, **kw):
-        pass
-
-    def remove_pid(self, *args, **kw):
-        if os.access(conf.pidfile, os.R_OK):
-            os.remove(conf.pidfile)
-        raise SystemExit
-
     def set_signal_handlers(self):
         import signal
         signal.signal(signal.SIGHUP, self.reload_config)


commit 205d67c00df3f0562469be8114bddc1a535a7202
Author: Jeroen van Meeuwen (Kolab Systems) <vanmeeuwen at kolabsys.com>
Date:   Thu May 24 17:19:11 2012 +0100

    Remove extraordinary debug logging statements and conform to the general <= 80 chars line width convention

diff --git a/wallace/module_resources.py b/wallace/module_resources.py
index 52156d4..bf0b59d 100644
--- a/wallace/module_resources.py
+++ b/wallace/module_resources.py
@@ -37,6 +37,7 @@ import modules
 import pykolab
 
 from pykolab.auth import Auth
+from pykolab.conf import Conf
 from pykolab.imap import IMAP
 from pykolab.xml import event_from_ical
 from pykolab.xml import event_from_string
@@ -54,7 +55,12 @@ def __init__():
     modules.register('resources', execute, description=description())
 
 def accept(filepath):
-    new_filepath = os.path.join(mybasepath, 'ACCEPT', os.path.basename(filepath))
+    new_filepath = os.path.join(
+            mybasepath,
+            'ACCEPT',
+            os.path.basename(filepath)
+        )
+
     os.rename(filepath, new_filepath)
     filepath = new_filepath
     exec('modules.cb_action_ACCEPT(%r, %r)' % ('resources',filepath))
@@ -107,7 +113,12 @@ def execute(*args, **kw):
             return
     else:
         # Move to incoming
-        new_filepath = os.path.join(mybasepath, 'incoming', os.path.basename(filepath))
+        new_filepath = os.path.join(
+                mybasepath,
+                'incoming',
+                os.path.basename(filepath)
+            )
+
         os.rename(filepath, new_filepath)
         filepath = new_filepath
 
@@ -155,7 +166,12 @@ def execute(*args, **kw):
             if resource_attrs.has_key('uniquemember'):
                 resources[resource_record] = resource_attrs
                 for uniquemember in resource_attrs['uniquemember']:
-                    resource_attrs = auth.get_entry_attributes(None, uniquemember, ['*'])
+                    resource_attrs = auth.get_entry_attributes(
+                            None,
+                            uniquemember,
+                            ['*']
+                        )
+
                     if 'kolabsharedfolder' in [x.lower() for x in resource_attrs['objectclass']]:
                         resources[uniquemember] = resource_attrs
                         resources[uniquemember]['memberof'] = resource_record
@@ -168,6 +184,8 @@ def execute(*args, **kw):
     # conflict.
     #
     # Store the (first) conflicting event(s) alongside the resource information.
+    start = time.time()
+
     for resource in resources.keys():
         if not resources[resource].has_key('kolabtargetfolder'):
             continue
@@ -177,7 +195,10 @@ def execute(*args, **kw):
         resources[resource]['conflict'] = False
         resources[resource]['conflicting_events'] = []
 
-        log.debug(_("Checking events in resource folder %r") % (mailbox), level=8)
+        log.debug(
+                _("Checking events in resource folder %r") % (mailbox),
+                level=8
+            )
 
         try:
             imap.imap.m.select(mailbox)
@@ -187,12 +208,18 @@ def execute(*args, **kw):
 
         typ, data = imap.imap.m.search(None, 'ALL')
 
+        num_messages = len(data[0].split())
+
         for num in data[0].split():
             # For efficiency, makes the routine non-deterministic
             if resources[resource]['conflict']:
                 continue
 
-            log.debug(_("Fetching message UID %r from folder %r") %(num, mailbox), level=9)
+            log.debug(
+                    _("Fetching message UID %r from folder %r") % (num, mailbox),
+                    level=9
+                )
+
             typ, data = imap.imap.m.fetch(num, '(RFC822)')
 
             event_message = message_from_string(data[0][1])
@@ -204,71 +231,78 @@ def execute(*args, **kw):
                         event = pykolab.xml.event_from_string(payload)
 
                         for itip in itip_events:
-                            log.debug(_("comparing %r to event %r (%r message UID %r)") % (itip, event.get_uid(), mailbox, num), level=9)
-                            log.debug(_("  event %r start: %r") % (event.get_uid(),event.get_start()), level=9)
-                            log.debug(_("  event %r end: %r") % (event.get_uid(),event.get_end()), level=9)
-
                             _es = event.get_start()
                             _is = itip['start'].dt
 
                             if type(_es) == 'datetime.date' or not hasattr(_es, 'hour'):
-                                log.debug(_("_es is datetime.date"))
-                                _es = datetime.datetime(_es.year, _es.month, _es.day, 0, 0, 0)
+                                _es = datetime.datetime(
+                                        _es.year, _es.month, _es.day, 0, 0, 0
+                                    )
 
                             if type(_is) == 'datetime.date' or not hasattr(_is, 'hour'):
-                                log.debug(_("_is is datetime.date"))
-                                _is = datetime.datetime(_is.year, _is.month, _is.day, 0, 0, 0)
+                                _is = datetime.datetime(
+                                        _is.year, _is.month, _is.day, 0, 0, 0
+                                    )
 
                             _ee = event.get_end()
                             _ie = itip['end'].dt
 
                             if type(_ee) == 'datetime.date' or not hasattr(_ee, 'hour'):
-                                log.debug(_("_ee is datetime.date"))
-                                _ee = datetime.datetime(_ee.year, _ee.month, _ee.day, 0, 0, 0)
+                                _ee = datetime.datetime(
+                                        _ee.year, _ee.month, _ee.day, 0, 0, 0
+                                    )
 
                             if type(_ie) == 'datetime.date' or not hasattr(_ie, 'hour'):
-                                log.debug(_("_ie is datetime.date"))
-                                _ie = datetime.datetime(_ie.year, _ie.month, _ie.day, 0, 0, 0)
-
-                            log.debug(_("Raw event and itip data:"))
-                            log.debug(_("_es: %r") %(_es))
-                            log.debug(_("_is: %r") %(_is))
-                            log.debug(_("_ee: %r") %(_ee))
-                            log.debug(_("_ie: %r") %(_ie))
+                                _ie = datetime.datetime(
+                                        _ie.year, _ie.month, _ie.day, 0, 0, 0
+                                    )
 
                             if _es < _is:
                                 if _es <= _ie:
                                     if _ee <= _is:
                                         conflict = False
                                     else:
-                                        log.debug(_("Event %r ends later than invitation") % (event.get_uid()), level=9)
                                         conflict = True
                                 else:
-                                    log.debug(_("Event %r starts before invitation ends") % (event.get_uid()), level=9)
                                     conflict = True
                             elif _es == _is:
-                                log.debug(_("Event %r and invitation start at the same time") % (event.get_uid()), level=9)
                                 conflict = True
                             else: # _es > _is
                                 if _es <= _ie:
-                                    log.debug(_("Event %r starts before invitation ends") % (event.get_uid()), level=9)
                                     conflict = True
                                 else:
                                     conflict = False
 
                             if conflict:
-                                log.debug(_("Conflict with event %r") % (event.get_uid()), level=8)
+                                log.info(
+                                        _("Event %r conflicts with event " + \
+                                            "%r") % (
+                                                itip['xml'].get_uid(),
+                                                event.get_uid()
+                                            )
+                                    )
+
                                 resources[resource]['conflicting_events'].append(event)
                                 resources[resource]['conflict'] = True
 
-        log.debug(_("Resource status information: %r") % (resources[resource]), level=8)
+    end = time.time()
 
+    log.debug(
+            _("start: %r, end: %r, total: %r, messages: %r") % (
+                    start, end, (end-start), num_messages
+                ),
+            level=1
+        )
 
     for resource in resources.keys():
         log.debug(_("Polling for resource %r") % (resource), level=9)
 
         if not resources.has_key(resource):
-            log.debug(_("Resource %r has been popped from the list") % (resource), level=9)
+            log.debug(
+                    _("Resource %r has been popped from the list") % (resource),
+                    level=9
+                )
+
             continue
 
         if not resources[resource].has_key('conflicting_events'):
@@ -280,9 +314,14 @@ def execute(*args, **kw):
             for itip_event in itip_events:
                 # Now we have the event that was conflicting
                 if resources[resource]['mail'] in [a.get_email() for a in itip_event['xml'].get_attendees()]:
-                    itip_event['xml'].set_attendee_participant_status([a for a in itip_event['xml'].get_attendees() if a.get_email() == resources[resource]['mail']][0], "DECLINED")
+                    itip_event['xml'].set_attendee_participant_status(
+                            [a for a in itip_event['xml'].get_attendees() if a.get_email() == resources[resource]['mail']][0],
+                            "DECLINED"
+                        )
 
                     send_response(resources[resource]['mail'], itip_events)
+                    # TODO: Accept the message to the other attendees
+
                 else:
                     # This must have been a resource collection originally.
                     # We have inserted the reference to the original resource
@@ -303,6 +342,7 @@ def execute(*args, **kw):
                             )
 
                         send_response(original_resource['mail'], itip_events)
+                        # TODO: Accept the message to the other attendees
 
         else:
             # No conflicts, go accept
@@ -313,8 +353,19 @@ def execute(*args, **kw):
                             "ACCEPTED"
                         )
 
-                    log.debug(_("Adding event to %r") % (resources[resource]['kolabtargetfolder']), level=9)
-                    imap.imap.m.append(resources[resource]['kolabtargetfolder'], None, None, itip_event['xml'].to_message().as_string())
+                    log.debug(
+                            _("Adding event to %r") % (
+                                    resources[resource]['kolabtargetfolder']
+                                ),
+                            level=9
+                        )
+
+                    imap.imap.m.append(
+                            resources[resource]['kolabtargetfolder'],
+                            None,
+                            None,
+                            itip_event['xml'].to_message().as_string()
+                        )
 
                     send_response(resources[resource]['mail'], itip_events)
 
@@ -337,8 +388,19 @@ def execute(*args, **kw):
                                 "ACCEPTED"
                             )
 
-                        log.debug(_("Adding event to %r") % (_target_resource['kolabtargetfolder']), level=9)
-                        imap.imap.m.append(_target_resource['kolabtargetfolder'], None, None, itip_event['xml'].to_message().as_string())
+                        log.debug(
+                                _("Adding event to %r") % (
+                                        _target_resource['kolabtargetfolder']
+                                    ),
+                                level=9
+                            )
+
+                        imap.imap.m.append(
+                                _target_resource['kolabtargetfolder'],
+                                None,
+                                None,
+                                itip_event['xml'].to_message().as_string()
+                            )
 
                         send_response(original_resource['mail'], itip_events)
 
@@ -460,20 +522,42 @@ def resource_records_from_itip_events(itip_events):
     attendees = [x.split(':')[-1] for x in attendees_raw]
 
     for attendee in attendees:
-        log.debug(_("Checking if attendee %r is a resource (collection)") % (attendee), level=8)
+        log.debug(
+                _("Checking if attendee %r is a resource (collection)") % (
+                        attendee
+                    ),
+                level=8
+            )
+
         _resource_records = auth.find_resource(attendee)
 
         if isinstance(_resource_records, list):
             if len(_resource_records) == 0:
-                log.debug(_("No resource (collection) records found for %r") % (attendee), level=9)
+                log.debug(
+                        _("No resource (collection) records found for %r") % (
+                                attendee
+                            ),
+                        level=9
+                    )
+
             else:
-                log.debug(_("Resource record(s): %r") % (_resource_records), level=8)
+                log.debug(
+                        _("Resource record(s): %r") % (_resource_records),
+                        level=8
+                    )
+
                 resource_records.extend(_resource_records)
         elif isinstance(_resource_records, basestring):
+            log.debug(
+                    _("Resource record: %r") % (_resource_records),
+                    level=8
+                )
+
             resource_records.append(_resource_records)
-            log.debug(_("Resource record: %r") % (_resource_records), level=8)
         else:
-            log.warning(_("Resource reservation made but no resource records found"))
+            log.warning(
+                    _("Resource reservation made but no resource records found")
+                )
 
     # TODO: Escape the non-implementation of the free-form, undefined RESOURCES
     # list(s) in iTip. We don't know how to handle this yet!
@@ -489,21 +573,45 @@ def resource_records_from_itip_events(itip_events):
     #
     resources = [x.split(':')[-1] for x in resources_raw]
     for resource in resources:
-        log.debug(_("Checking if resource %r is a resource (collection)") % (resource), level=8)
+        log.debug(
+                _("Checking if resource %r is a resource (collection)") % (
+                        resource
+                    ),
+                level=8
+            )
+
         _resource_records = auth.find_resource(resource)
         if isinstance(_resource_records, list):
             if len(_resource_records) == 0:
-                log.debug(_("No resource (collection) records found for %r") % (resource), level=8)
+                log.debug(
+                        _("No resource (collection) records found for %r") % (
+                                resource
+                            ),
+                        level=8
+                    )
+
             else:
-                log.debug(_("Resource record(s): %r") % (_resource_records), level=8)
+                log.debug(
+                        _("Resource record(s): %r") % (_resource_records),
+                        level=8
+                    )
+
                 resource_records.extend(_resource_records)
         elif isinstance(_resource_records, basestring):
             resource_records.append(_resource_records)
             log.debug(_("Resource record: %r") % (_resource_records), level=8)
         else:
-            log.warning(_("Resource reservation made but no resource records found"))
+            log.warning(
+                    _("Resource reservation made but no resource records found")
+                )
 
-    log.debug(_("The following resources are being referred to in the iTip: %r") % (resource_records), level=8)
+    log.debug(
+            _("The following resources are being referred to in the " + \
+                "iTip: %r") % (
+                    resource_records
+                ),
+            level=8
+        )
 
     return resource_records
 
@@ -516,7 +624,7 @@ def send_response(from_address, itip_events):
 
     for itip_event in itip_events:
         attendee = [a for a in itip_event['xml'].get_attendees() if a.get_email() == from_address][0]
-        participant_status = itip_event['xml'].get_attendee_participant_status(attendee)
+        participant_status = itip_event['xml'].get_ical_attendee_participant_status(attendee)
         message = itip_event['xml'].to_message_itip(from_address, method="REPLY", participant_status=participant_status)
         smtp.sendmail(message['From'], message['To'], message.as_string())
     smtp.quit()


commit 0a287401ae7a29a9e30696f4774f160ca751a34d
Author: Jeroen van Meeuwen (Kolab Systems) <vanmeeuwen at kolabsys.com>
Date:   Thu May 24 12:15:13 2012 +0100

    Extend tests to 43 in total (attendee, event)

diff --git a/tests/test-002-attendee.py b/tests/test-002-attendee.py
index 74f680b..15ce83f 100644
--- a/tests/test-002-attendee.py
+++ b/tests/test-002-attendee.py
@@ -17,5 +17,80 @@ class TestEventXML(unittest.TestCase):
         self.attendee.set_name(name)
         self.assertEqual(self.attendee.get_name(), name)
 
+    def test_004_default_participant_status(self):
+        self.assertEqual(self.attendee.get_participant_status(), 0)
+
+    def test_005_participant_status_map_length(self):
+        self.assertEqual(len(self.attendee.participant_status_map.keys()), 5)
+
+    def test_006_participant_status_map_forward_lookup(self):
+        # Forward lookups
+        self.assertEqual(self.attendee.participant_status_map["NEEDS-ACTION"], 0)
+        self.assertEqual(self.attendee.participant_status_map["ACCEPTED"], 1)
+        self.assertEqual(self.attendee.participant_status_map["DECLINED"], 2)
+        self.assertEqual(self.attendee.participant_status_map["TENTATIVE"], 3)
+        self.assertEqual(self.attendee.participant_status_map["DELEGATED"], 4)
+
+    def test_007_participant_status_map_reverse_lookup(self):
+        # Reverse lookups
+        self.assertEqual([k for k,v in self.attendee.participant_status_map.iteritems() if v == 0][0], "NEEDS-ACTION")
+        self.assertEqual([k for k,v in self.attendee.participant_status_map.iteritems() if v == 1][0], "ACCEPTED")
+        self.assertEqual([k for k,v in self.attendee.participant_status_map.iteritems() if v == 2][0], "DECLINED")
+        self.assertEqual([k for k,v in self.attendee.participant_status_map.iteritems() if v == 3][0], "TENTATIVE")
+        self.assertEqual([k for k,v in self.attendee.participant_status_map.iteritems() if v == 4][0], "DELEGATED")
+
+    def test_008_default_rsvp(self):
+        self.assertEqual(self.attendee.get_rsvp(), 0)
+
+    def test_009_rsvp_map_length(self):
+        self.assertEqual(len(self.attendee.rsvp_map.keys()), 2)
+
+    def test_010_rsvp_map_forward_lookup_boolean(self):
+        self.assertEqual(self.attendee.rsvp_map["TRUE"], True)
+        self.assertEqual(self.attendee.rsvp_map["FALSE"], False)
+
+    def test_011_rsvp_map_forward_lookup_integer(self):
+        self.assertEqual(self.attendee.rsvp_map["TRUE"], 1)
+        self.assertEqual(self.attendee.rsvp_map["FALSE"], 0)
+
+    def test_012_rsvp_map_reverse_lookup_boolean(self):
+        self.assertEqual([k for k,v in self.attendee.rsvp_map.iteritems() if v == True][0], "TRUE")
+        self.assertEqual([k for k,v in self.attendee.rsvp_map.iteritems() if v == False][0], "FALSE")
+
+    def test_013_rsvp_map_reverse_lookup_integer(self):
+        self.assertEqual([k for k,v in self.attendee.rsvp_map.iteritems() if v == 1][0], "TRUE")
+        self.assertEqual([k for k,v in self.attendee.rsvp_map.iteritems() if v == 0][0], "FALSE")
+
+    def test_014_default_role(self):
+        self.assertEqual(self.attendee.get_role(), 0)
+
+    def test_015_role_map_length(self):
+        self.assertEqual(len(self.attendee.role_map.keys()), 4)
+
+    def test_016_role_map_forward_lookup(self):
+        self.assertEqual(self.attendee.role_map["REQ-PARTICIPANT"], 0)
+        self.assertEqual(self.attendee.role_map["CHAIR"], 1)
+        self.assertEqual(self.attendee.role_map["OPTIONAL"], 2)
+        self.assertEqual(self.attendee.role_map["NONPARTICIPANT"], 3)
+
+    def test_017_role_map_reverse_lookup(self):
+        self.assertEqual([k for k,v in self.attendee.role_map.iteritems() if v == 0][0], "REQ-PARTICIPANT")
+        self.assertEqual([k for k,v in self.attendee.role_map.iteritems() if v == 1][0], "CHAIR")
+        self.assertEqual([k for k,v in self.attendee.role_map.iteritems() if v == 2][0], "OPTIONAL")
+        self.assertEqual([k for k,v in self.attendee.role_map.iteritems() if v == 3][0], "NONPARTICIPANT")
+
+    def test_015_cutype_map_length(self):
+        self.assertEqual(len(self.attendee.cutype_map.keys()), 3)
+
+    def test_016_cutype_map_forward_lookup(self):
+        self.assertEqual(self.attendee.cutype_map["GROUP"], 1)
+        self.assertEqual(self.attendee.cutype_map["INDIVIDUAL"], 2)
+        self.assertEqual(self.attendee.cutype_map["RESOURCE"], 3)
+
+    def test_017_cutype_map_reverse_lookup(self):
+        self.assertEqual([k for k,v in self.attendee.cutype_map.iteritems() if v == 1][0], "GROUP")
+        self.assertEqual([k for k,v in self.attendee.cutype_map.iteritems() if v == 2][0], "INDIVIDUAL")
+        self.assertEqual([k for k,v in self.attendee.cutype_map.iteritems() if v == 3][0], "RESOURCE")
+
 if __name__ == '__main__':
     unittest.main()
diff --git a/tests/test-003-event.py b/tests/test-003-event.py
index 3a6a863..031f4a1 100644
--- a/tests/test-003-event.py
+++ b/tests/test-003-event.py
@@ -38,22 +38,18 @@ class TestEventXML(unittest.TestCase):
         self.assertEqual([x.get_email() for x in self.event.get_attendees()], ["john at doe.org", "jane at doe.org"])
 
     def test_007_get_attendee_by_email(self):
-        attendee = self.event.get_attendee_by_email("jane at doe.org")
-        self.assertIsInstance(attendee, Attendee)
-
-        attendee = self.event.get_attendee("jane at doe.org")
-        self.assertIsInstance(attendee, Attendee)
+        self.assertIsInstance(self.event.get_attendee_by_email("jane at doe.org"), Attendee)
+        self.assertIsInstance(self.event.get_attendee("jane at doe.org"), Attendee)
 
+    def test_007_get_nonexistent_attendee_by_email(self):
         self.assertRaises(ValueError, self.event.get_attendee_by_email, "nosuchattendee at invalid.domain")
         self.assertRaises(ValueError, self.event.get_attendee, "nosuchattendee at invalid.domain")
 
     def test_008_get_attendee_by_name(self):
-        attendee = self.event.get_attendee_by_name("Doe, Jane")
-        self.assertIsInstance(attendee, Attendee)
-
-        attendee = self.event.get_attendee("Doe, Jane")
-        self.assertIsInstance(attendee, Attendee)
+        self.assertIsInstance(self.event.get_attendee_by_name("Doe, Jane"), Attendee)
+        self.assertIsInstance(self.event.get_attendee("Doe, Jane"), Attendee)
 
+    def test_008_get_nonexistent_attendee_by_name(self):
         self.assertRaises(ValueError, self.event.get_attendee_by_name, "Houdini, Harry")
         self.assertRaises(ValueError, self.event.get_attendee, "Houdini, Harry")
 
@@ -66,5 +62,16 @@ class TestEventXML(unittest.TestCase):
     def test_011_attendee_equality(self):
         self.assertEqual(self.event.get_attendee("jane at doe.org").get_email(), "jane at doe.org")
 
+    def test_012_delegate_new_attendee(self):
+        self.event.delegate("jane at doe.org", "max at imum.com")
+
+    def test_013_delegatee_is_now_attendee(self):
+        self.assertIsInstance(self.event.get_attendee("max at imum.com"), Attendee)
+
+    def test_014_delegate_attendee_adds(self):
+        self.assertEqual(len(self.event.get_attendee("jane at doe.org").get_delegated_to()), 1)
+        self.event.delegate("jane at doe.org", "john at doe.org")
+        self.assertEqual(len(self.event.get_attendee("jane at doe.org").get_delegated_to()), 2)
+
 if __name__ == '__main__':
     unittest.main()





More information about the commits mailing list