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