From 38119791db219b94b4e313b0d7c810590b5a7258 Mon Sep 17 00:00:00 2001 From: Barry Warsaw Date: Sun, 18 Oct 2009 19:13:24 -0400 Subject: Infrastructure for testing the Connection class, and for counting the number of session start and end events in the server. --- src/mailman/testing/layers.py | 18 ++---------------- 1 file changed, 2 insertions(+), 16 deletions(-) (limited to 'src/mailman/testing/layers.py') diff --git a/src/mailman/testing/layers.py b/src/mailman/testing/layers.py index d4db9ebf2..034e26f83 100644 --- a/src/mailman/testing/layers.py +++ b/src/mailman/testing/layers.py @@ -35,7 +35,6 @@ import logging import datetime import tempfile -from lazr.smtptest.controller import QueueController from pkg_resources import resource_string from textwrap import dedent from urllib2 import urlopen, URLError @@ -48,6 +47,7 @@ from mailman.i18n import _ from mailman.interfaces.domain import IDomainManager from mailman.interfaces.messages import IMessageStore from mailman.testing.helpers import TestableMaster +from mailman.testing.mta import SessionCountingController from mailman.utilities.datetime import factory from mailman.utilities.string import expand @@ -210,20 +210,6 @@ class ConfigLayer(MockAndMonkeyLayer): -class ExtendedQueueController(QueueController): - """QueueController with a little extra API.""" - - @property - def messages(self): - """Return all the messages received by the SMTP server.""" - for message in self: - yield message - - def clear(self): - """Clear all the messages from the queue.""" - list(self) - - class SMTPLayer(ConfigLayer): """Layer for starting, stopping, and accessing a test SMTP server.""" @@ -234,7 +220,7 @@ class SMTPLayer(ConfigLayer): assert cls.smtpd is None, 'Layer already set up' host = config.mta.smtp_host port = int(config.mta.smtp_port) - cls.smtpd = ExtendedQueueController(host, port) + cls.smtpd = SessionCountingController(host, port) cls.smtpd.start() @classmethod -- cgit v1.2.3-70-g09d2 From 34e27b816aa120e81a8e970ac1d8847384bd3edf Mon Sep 17 00:00:00 2001 From: Barry Warsaw Date: Sun, 18 Oct 2009 20:05:15 -0400 Subject: Show that a max_sessions_per_connection == 0 means there's an unlimited number of sessions per connection (or at least 10 ). --- src/mailman/mta/connection.py | 6 +- src/mailman/mta/docs/connection.txt | 130 +++++++++++++++++++++++++++++++++--- src/mailman/testing/layers.py | 4 +- src/mailman/testing/mta.py | 34 +++++----- 4 files changed, 144 insertions(+), 30 deletions(-) (limited to 'src/mailman/testing/layers.py') diff --git a/src/mailman/mta/connection.py b/src/mailman/mta/connection.py index 922c492e9..105d25afb 100644 --- a/src/mailman/mta/connection.py +++ b/src/mailman/mta/connection.py @@ -54,6 +54,7 @@ class Connection: self._host = host self._port = port self._sessions_per_connection = sessions_per_connection + self._session_count = None self._connection = None def _connect(self): @@ -61,6 +62,7 @@ class Connection: self._connection = smtplib.SMTP() log.debug('Connecting to %s:%s', self._host, self._port) self._connection.connect(self._host, self._port) + self._session_count = self._sessions_per_connection def sendmail(self, envsender, recips, msgtext): """Mimic `smtplib.SMTP.sendmail`.""" @@ -74,11 +76,11 @@ class Connection: self.quit() raise # This session has been successfully completed. - self._sessions_per_connection -= 1 + self._session_count -= 1 # By testing exactly for equality to 0, we automatically handle the # case for SMTP_MAX_SESSIONS_PER_CONNECTION <= 0 meaning never close # the connection. We won't worry about wraparound . - if self._sessions_per_connection == 0: + if self._session_count == 0: self.quit() return results diff --git a/src/mailman/mta/docs/connection.txt b/src/mailman/mta/docs/connection.txt index d57e301d6..8924826c8 100644 --- a/src/mailman/mta/docs/connection.txt +++ b/src/mailman/mta/docs/connection.txt @@ -4,22 +4,23 @@ MTA connections Outgoing connections to the outgoing mail transport agent (MTA) are mitigated through a Connection class, which can transparently manage multiple sessions -to a single physical MTA. +in a single connection. >>> from mailman.mta.connection import Connection -The number of connections per session is specified when the Connection object -is created, as is the host and port number of the SMTP server. +The number of sessions per connections is specified when the Connection object +is created, as is the host and port number of the SMTP server. Zero means +there's an unlimited number of sessions per connection. >>> connection = Connection( - ... config.mta.smtp_host, int(config.mta.smtp_port), 1) + ... config.mta.smtp_host, int(config.mta.smtp_port), 0) At the start, there have been no connections to the server. - >>> smtpd.get_session_count() + >>> smtpd.get_connection_count() 0 -By sending a message to the server, a session is started. +By sending a message to the server, a connection is opened. >>> connection.sendmail('anne@example.com', ['bart@example.com'], """\ ... From: anne@example.com @@ -29,10 +30,10 @@ By sending a message to the server, a session is started. ... """) {} - >>> smtpd.get_session_count() + >>> smtpd.get_connection_count() 1 -We can reset the session count back to zero. +We can reset the connection count back to zero. >>> from smtplib import SMTP >>> def reset(): @@ -41,5 +42,116 @@ We can reset the session count back to zero. ... smtpd.docmd('RSET') >>> reset() - >>> smtpd.get_session_count() + >>> smtpd.get_connection_count() 0 + + >>> connection.quit() + + +Sessions per connection +======================= + +Let's say we specify a maximum number of sessions per connection of 2. When +the third message is sent, the connection is torn down and a new one is +created. + +The connection count starts at zero. + + >>> connection = Connection( + ... config.mta.smtp_host, int(config.mta.smtp_port), 2) + + >>> smtpd.get_connection_count() + 0 + +We send two messages through the Connection object. Only one connection is +opened. + + >>> connection.sendmail('anne@example.com', ['bart@example.com'], """\ + ... From: anne@example.com + ... To: bart@example.com + ... Subject: aardvarks + ... + ... """) + {} + + >>> smtpd.get_connection_count() + 1 + + >>> connection.sendmail('anne@example.com', ['bart@example.com'], """\ + ... From: anne@example.com + ... To: bart@example.com + ... Subject: aardvarks + ... + ... """) + {} + + >>> smtpd.get_connection_count() + 1 + +The third message causes a third session, which exceeds the maximum. So the +current connection is closed and a new one opened. + + >>> connection.sendmail('anne@example.com', ['bart@example.com'], """\ + ... From: anne@example.com + ... To: bart@example.com + ... Subject: aardvarks + ... + ... """) + {} + + >>> smtpd.get_connection_count() + 2 + +A fourth message does not cause a new connection to be made. + + >>> connection.sendmail('anne@example.com', ['bart@example.com'], """\ + ... From: anne@example.com + ... To: bart@example.com + ... Subject: aardvarks + ... + ... """) + {} + + >>> smtpd.get_connection_count() + 2 + +But a fifth one does. + + >>> connection.sendmail('anne@example.com', ['bart@example.com'], """\ + ... From: anne@example.com + ... To: bart@example.com + ... Subject: aardvarks + ... + ... """) + {} + + >>> smtpd.get_connection_count() + 3 + + +No maximum +========== + +A value of zero means that there is an unlimited number of sessions per +connection. + + >>> connection = Connection( + ... config.mta.smtp_host, int(config.mta.smtp_port), 0) + >>> reset() + +Even after ten messages are sent, there's still been only one connection to +the server. + + >>> connection.debug = True + >>> for i in range(10): + ... # Ignore the results. + ... results = connection.sendmail( + ... 'anne@example.com', ['bart@example.com'], """\ + ... From: anne@example.com + ... To: bart@example.com + ... Subject: aardvarks + ... + ... """) + + >>> smtpd.get_connection_count() + 1 diff --git a/src/mailman/testing/layers.py b/src/mailman/testing/layers.py index 034e26f83..70b0ae9b1 100644 --- a/src/mailman/testing/layers.py +++ b/src/mailman/testing/layers.py @@ -47,7 +47,7 @@ from mailman.i18n import _ from mailman.interfaces.domain import IDomainManager from mailman.interfaces.messages import IMessageStore from mailman.testing.helpers import TestableMaster -from mailman.testing.mta import SessionCountingController +from mailman.testing.mta import ConnectionCountingController from mailman.utilities.datetime import factory from mailman.utilities.string import expand @@ -220,7 +220,7 @@ class SMTPLayer(ConfigLayer): assert cls.smtpd is None, 'Layer already set up' host = config.mta.smtp_host port = int(config.mta.smtp_port) - cls.smtpd = SessionCountingController(host, port) + cls.smtpd = ConnectionCountingController(host, port) cls.smtpd.start() @classmethod diff --git a/src/mailman/testing/mta.py b/src/mailman/testing/mta.py index 28e5ec77c..62070cc5d 100644 --- a/src/mailman/testing/mta.py +++ b/src/mailman/testing/mta.py @@ -56,8 +56,8 @@ class FakeMTA: -class SessionCountingChannel(Channel): - """Count the number of SMTP sessions opened and closed.""" +class StatisticsChannel(Channel): + """A channel that can answers to the fake STAT command.""" def smtp_STAT(self, arg): """Cause the server to send statistics to its controller.""" @@ -66,13 +66,13 @@ class SessionCountingChannel(Channel): -class SessionCountingServer(QueueServer): - """Count the number of SMTP sessions opened and closed.""" +class ConnectionCountingServer(QueueServer): + """Count the number of SMTP connections opened.""" def __init__(self, host, port, queue, oob_queue): """See `lazr.smtptest.server.QueueServer`.""" QueueServer.__init__(self, host, port, queue) - self.session_count = 0 + self._connection_count = 0 # The out-of-band queue is where the server sends statistics to the # controller upon request. self._oob_queue = oob_queue @@ -80,25 +80,25 @@ class SessionCountingServer(QueueServer): def handle_accept(self): """See `lazr.smtp.server.Server`.""" connection, address = self.accept() - self.session_count += 1 - log.info('[SessionCountingServer] accepted: %s', address) - SessionCountingChannel(self, connection, address) + self._connection_count += 1 + log.info('[ConnectionCountingServer] accepted: %s', address) + StatisticsChannel(self, connection, address) def reset(self): """See `lazr.smtp.server.Server`.""" QueueServer.reset(self) - self.session_count = 0 + self._connection_count = 0 def send_statistics(self): """Send the current connection statistics to the controller.""" # Do not count the connection caused by the STAT connect. - self.session_count -= 1 - self._oob_queue.put(self.session_count) + self._connection_count -= 1 + self._oob_queue.put(self._connection_count) -class SessionCountingController(QueueController): - """Count the number of SMTP sessions opened and closed.""" +class ConnectionCountingController(QueueController): + """Count the number of SMTP connections opened.""" def __init__(self, host, port): """See `lazr.smtptest.controller.QueueController`.""" @@ -107,7 +107,7 @@ class SessionCountingController(QueueController): def _make_server(self, host, port): """See `lazr.smtptest.controller.QueueController`.""" - self.server = SessionCountingServer( + self.server = ConnectionCountingServer( host, port, self.queue, self.oob_queue) def start(self): @@ -117,10 +117,10 @@ class SessionCountingController(QueueController): # method causes a connection to occur. self.reset() - def get_session_count(self): - """Retrieve the number of sessions. + def get_connection_count(self): + """Retrieve the number of connections. - :return: The number of sessions that have been opened. + :return: The number of connections to the server that have been made. :rtype: integer """ smtpd = self._connect() -- cgit v1.2.3-70-g09d2 From 1e8d8bfdb64968763a6a4fbd74ad912eb4c6c0b6 Mon Sep 17 00:00:00 2001 From: Barry Warsaw Date: Mon, 2 Nov 2009 20:07:53 -0500 Subject: Fix all tests except outgoing.txt and common.txt --- src/mailman/archiving/mailarchive.py | 2 +- src/mailman/docs/pipelines.txt | 6 +++--- src/mailman/docs/registration.txt | 2 +- src/mailman/docs/requests.txt | 26 +++++++++++++------------- src/mailman/email/message.py | 4 ++-- src/mailman/mta/docs/bulk.txt | 1 - src/mailman/mta/docs/verp.txt | 1 - src/mailman/pipeline/docs/acknowledge.txt | 4 ++-- src/mailman/pipeline/docs/avoid-duplicates.txt | 5 +++-- src/mailman/pipeline/docs/decorate.txt | 21 --------------------- src/mailman/testing/layers.py | 2 +- 11 files changed, 26 insertions(+), 48 deletions(-) (limited to 'src/mailman/testing/layers.py') diff --git a/src/mailman/archiving/mailarchive.py b/src/mailman/archiving/mailarchive.py index a5eb27db0..b3d7f4506 100644 --- a/src/mailman/archiving/mailarchive.py +++ b/src/mailman/archiving/mailarchive.py @@ -84,4 +84,4 @@ class MailArchive: config.switchboards['out'].enqueue( msg, listname=mlist.fqdn_listname, - recips=[config.archiver.mail_archive.recipient]) + recipients=[config.archiver.mail_archive.recipient]) diff --git a/src/mailman/docs/pipelines.txt b/src/mailman/docs/pipelines.txt index 5ad6ba243..51dfd7a61 100644 --- a/src/mailman/docs/pipelines.txt +++ b/src/mailman/docs/pipelines.txt @@ -66,7 +66,7 @@ However there are currently no recipients for this message. >>> dump_msgdata(msgdata) original_sender : aperson@example.com origsubj : My first post - recips : set([]) + recipients : set([]) stripped_subject: My first post And the message is now sitting in various other processing queues. @@ -103,7 +103,7 @@ And the message is now sitting in various other processing queues. _parsemsg : False original_sender : aperson@example.com origsubj : My first post - recips : set([]) + recipients : set([]) stripped_subject: My first post version : 3 @@ -148,7 +148,7 @@ This is the message that will actually get delivered to end recipients. listname : xtest@example.com original_sender : aperson@example.com origsubj : My first post - recips : set([]) + recipients : set([]) stripped_subject: My first post version : 3 diff --git a/src/mailman/docs/registration.txt b/src/mailman/docs/registration.txt index 09cc790ea..6b1ff0c18 100644 --- a/src/mailman/docs/registration.txt +++ b/src/mailman/docs/registration.txt @@ -158,7 +158,7 @@ message is sent to the user in order to verify the registered address. >>> dump_msgdata(qdata) _parsemsg : False nodecorate : True - recips : [u'aperson@example.com'] + recipients : [u'aperson@example.com'] reduced_list_headers: True version : 3 diff --git a/src/mailman/docs/requests.txt b/src/mailman/docs/requests.txt index 0113e2274..5dcfa922a 100644 --- a/src/mailman/docs/requests.txt +++ b/src/mailman/docs/requests.txt @@ -299,7 +299,7 @@ The message can be rejected, meaning it is bounced back to the sender. _parsemsg : False listname : alist@example.com nodecorate : True - recips : [u'aperson@example.org'] + recipients : [u'aperson@example.org'] reduced_list_headers: True version : 3 @@ -404,7 +404,7 @@ moderators. _parsemsg : False listname : alist@example.com nodecorate : True - recips : [u'zperson@example.com'] + recipients : [u'zperson@example.com'] reduced_list_headers: True version : 3 @@ -474,7 +474,7 @@ queue when the message is held. _parsemsg : False listname : alist@example.com nodecorate : True - recips : [u'alist-owner@example.com'] + recipients : [u'alist-owner@example.com'] reduced_list_headers: True tomoderators : True version : 3 @@ -529,7 +529,7 @@ subscriber. _parsemsg : False listname : alist@example.com nodecorate : True - recips : [u'cperson@example.org'] + recipients : [u'cperson@example.org'] reduced_list_headers: True version : 3 @@ -573,7 +573,7 @@ subscription and the fact that they may need to approve it. _parsemsg : False listname : alist@example.com nodecorate : True - recips : [u'alist-owner@example.com'] + recipients : [u'alist-owner@example.com'] reduced_list_headers: True tomoderators : True version : 3 @@ -590,7 +590,7 @@ at the recipient list. >>> qmsg_1, qdata_1 = dequeue(expected_count=2) >>> qmsg_2, qdata_2 = dequeue() - >>> if 'fperson@example.org' in qdata_1['recips']: + >>> if 'fperson@example.org' in qdata_1['recipients']: ... # The first message is the welcome message ... welcome_qmsg = qmsg_1 ... welcome_qdata = qdata_1 @@ -646,7 +646,7 @@ The welcome message is sent to the person who just subscribed. _parsemsg : False listname : alist@example.com nodecorate : True - recips : [u'fperson@example.org'] + recipients : [u'fperson@example.org'] reduced_list_headers: True verp : False version : 3 @@ -672,7 +672,7 @@ The admin message is sent to the moderators. envsender : changeme@example.com listname : alist@example.com nodecorate : True - recips : [] + recipients : [] reduced_list_headers: True version : 3 @@ -752,7 +752,7 @@ unsubscription holds can send the list's moderators an immediate notification. _parsemsg : False listname : alist@example.com nodecorate : True - recips : [u'alist-owner@example.com'] + recipients : [u'alist-owner@example.com'] reduced_list_headers: True tomoderators : True version : 3 @@ -810,7 +810,7 @@ and the person remains a member of the mailing list. _parsemsg : False listname : alist@example.com nodecorate : True - recips : [u'hperson@example.com'] + recipients : [u'hperson@example.com'] reduced_list_headers: True version : 3 @@ -834,7 +834,7 @@ change. >>> qmsg_1, qdata_1 = dequeue(expected_count=2) >>> qmsg_2, qdata_2 = dequeue() - >>> if 'gperson@example.com' in qdata_1['recips']: + >>> if 'gperson@example.com' in qdata_1['recipients']: ... # The first message is the goodbye message ... goodbye_qmsg = qmsg_1 ... goodbye_qdata = qdata_1 @@ -865,7 +865,7 @@ The goodbye message... _parsemsg : False listname : alist@example.com nodecorate : True - recips : [u'gperson@example.com'] + recipients : [u'gperson@example.com'] reduced_list_headers: True verp : False version : 3 @@ -890,6 +890,6 @@ The goodbye message... envsender : changeme@example.com listname : alist@example.com nodecorate : True - recips : [] + recipients : [] reduced_list_headers: True version : 3 diff --git a/src/mailman/email/message.py b/src/mailman/email/message.py index 984c58c76..458f4070c 100644 --- a/src/mailman/email/message.py +++ b/src/mailman/email/message.py @@ -204,7 +204,7 @@ class UserNotification(Message): virginq = config.switchboards['virgin'] # The message metadata better have a 'recip' attribute. enqueue_kws = dict( - recips=self.recips, + recipients=self.recips, nodecorate=True, reduced_list_headers=True, ) @@ -242,7 +242,7 @@ class OwnerNotification(UserNotification): # The message metadata better have a `recip' attribute virginq.enqueue(self, listname=mlist.fqdn_listname, - recips=self.recips, + recipients=self.recips, nodecorate=True, reduced_list_headers=True, envsender=self._sender, diff --git a/src/mailman/mta/docs/bulk.txt b/src/mailman/mta/docs/bulk.txt index e40bdce16..99f58e7a7 100644 --- a/src/mailman/mta/docs/bulk.txt +++ b/src/mailman/mta/docs/bulk.txt @@ -142,7 +142,6 @@ Bulk delivery The set of recipients for bulk delivery comes from the message metadata. If there are no calculated recipients, nothing gets sent. - >>> smtpd.clear() >>> mlist = create_list('test@example.com') >>> msg = message_from_string("""\ ... From: aperson@example.org diff --git a/src/mailman/mta/docs/verp.txt b/src/mailman/mta/docs/verp.txt index 11157d363..c7c14e714 100644 --- a/src/mailman/mta/docs/verp.txt +++ b/src/mailman/mta/docs/verp.txt @@ -41,7 +41,6 @@ No recipients The message metadata specifies the set of recipients to send this message to. If there are no recipients, there's nothing to do. - >>> smtpd.clear() >>> mlist = create_list('test@example.com') >>> msg = message_from_string("""\ ... From: aperson@example.org diff --git a/src/mailman/pipeline/docs/acknowledge.txt b/src/mailman/pipeline/docs/acknowledge.txt index 3b8316cab..c304bd8bf 100644 --- a/src/mailman/pipeline/docs/acknowledge.txt +++ b/src/mailman/pipeline/docs/acknowledge.txt @@ -110,7 +110,7 @@ The receipt will include the original message's subject in the response body, >>> virginq.files [] >>> sorted(qdata.items()) - [..., ('recips', [u'aperson@example.com']), ...] + [..., ('recipients', [u'aperson@example.com']), ...] >>> print qmsg.as_string() ... MIME-Version: 1.0 @@ -144,7 +144,7 @@ If there is no subject, then the receipt will use a generic message. >>> virginq.files [] >>> sorted(qdata.items()) - [..., ('recips', [u'aperson@example.com']), ...] + [..., ('recipients', [u'aperson@example.com']), ...] >>> print qmsg.as_string() MIME-Version: 1.0 ... diff --git a/src/mailman/pipeline/docs/avoid-duplicates.txt b/src/mailman/pipeline/docs/avoid-duplicates.txt index b6d0133fd..1493c4d04 100644 --- a/src/mailman/pipeline/docs/avoid-duplicates.txt +++ b/src/mailman/pipeline/docs/avoid-duplicates.txt @@ -24,7 +24,8 @@ Create some members we're going to use. >>> member_b = address_b.subscribe(mlist, MemberRole.member) >>> # This is the message metadata dictionary as it would be produced by >>> # the CalcRecips handler. - >>> recips = dict(recips=['aperson@example.com', 'bperson@example.com']) + >>> recips = dict( + ... recipients=['aperson@example.com', 'bperson@example.com']) Short circuiting @@ -126,7 +127,7 @@ Other headers checked for recipients include the To... ... """) >>> msgdata = recips.copy() >>> handler.process(mlist, msg, msgdata) - >>> sorted(msgdata['recips']) + >>> sorted(msgdata['recipients']) [u'bperson@example.com'] >>> print msg.as_string() From: Claire Person diff --git a/src/mailman/pipeline/docs/decorate.txt b/src/mailman/pipeline/docs/decorate.txt index 78b500409..246e67096 100644 --- a/src/mailman/pipeline/docs/decorate.txt +++ b/src/mailman/pipeline/docs/decorate.txt @@ -295,24 +295,3 @@ that the header and footer can be added as attachments. footer --BOUNDARY-- - - -Personalization -=============== - -A mailing list can be 'personalized', meaning that each message is unique for -each recipient. When the list is personalized, additional interpolation -variables are available, however the list of intended recipients must be -provided in the message data, otherwise an exception occurs. - - >>> process(mlist, None, dict(personalize=True)) - Traceback (most recent call last): - ... - AssertionError: The number of intended recipients must be exactly 1 - -And the number of intended recipients must be exactly 1. - - >>> process(mlist, None, dict(personalize=True, recipients=[1, 2, 3])) - Traceback (most recent call last): - ... - AssertionError: The number of intended recipients must be exactly 1 diff --git a/src/mailman/testing/layers.py b/src/mailman/testing/layers.py index 70b0ae9b1..4bc98161e 100644 --- a/src/mailman/testing/layers.py +++ b/src/mailman/testing/layers.py @@ -235,7 +235,7 @@ class SMTPLayer(ConfigLayer): @classmethod def testTearDown(cls): - pass + cls.smtpd.clear() -- cgit v1.2.3-70-g09d2