[CalendarServer-changes] [7581] CalendarServer/trunk/contrib/performance/loadtest

source_changes at macosforge.org source_changes at macosforge.org
Mon Jun 13 11:01:27 PDT 2011


Revision: 7581
          http://trac.macosforge.org/projects/calendarserver/changeset/7581
Author:   exarkun at twistedmatrix.com
Date:     2011-06-13 11:01:27 -0700 (Mon, 13 Jun 2011)
Log Message:
-----------
Factor VFREEBUSY request into its own method and make it more flexible so a profile can use it if we want

Modified Paths:
--------------
    CalendarServer/trunk/contrib/performance/loadtest/ical.py
    CalendarServer/trunk/contrib/performance/loadtest/request-data/sl_post_availability.request
    CalendarServer/trunk/contrib/performance/loadtest/test_ical.py

Modified: CalendarServer/trunk/contrib/performance/loadtest/ical.py
===================================================================
--- CalendarServer/trunk/contrib/performance/loadtest/ical.py	2011-06-13 18:00:44 UTC (rev 7580)
+++ CalendarServer/trunk/contrib/performance/loadtest/ical.py	2011-06-13 18:01:27 UTC (rev 7581)
@@ -18,7 +18,7 @@
 from uuid import uuid4
 from operator import getitem
 from pprint import pformat
-from datetime import datetime
+from datetime import timedelta, datetime
 from urlparse import urlparse, urlunparse
 
 from xml.etree import ElementTree
@@ -143,7 +143,7 @@
     # The default interval, used if none is specified in external
     # configuration.  This is also the actual value used by Snow
     # Leopard iCal.
-    CALENDAR_HOME_POLL_INTERVAL = 60 * 15
+    CALENDAR_HOME_POLL_INTERVAL = 15 * 60
 
     _STARTUP_PRINCIPAL_PROPFIND = loadRequestBody('sl_startup_principal_propfind')
     _STARTUP_PRINCIPALS_REPORT = loadRequestBody('sl_startup_principals_report')
@@ -350,7 +350,7 @@
                 response = yield self._eventReport(url, responseHref)
                 body = yield readBody(response)
                 res = self._parseMultiStatus(body)[responseHref]
-                if res.getStatus() is not None and " 404 " not in res.getStatus():
+                if res.getStatus() is None or " 404 " not in res.getStatus():
                     text = res.getTextProperties()
                     etag = text[davxml.getetag]
                     try:
@@ -561,26 +561,11 @@
         d.addCallback(narrowed)
         def specific(ignored):
             # Now learn about the attendee's availability
-            uid = vevent.contents[u'vevent'][0].contents[u'uid'][0].value
-            start = vevent.contents[u'vevent'][0].contents[u'dtstart'][0].value
-            end = vevent.contents[u'vevent'][0].contents[u'dtend'][0].value
-            now = datetime.now()
-            uri = self.root + 'calendars/__uids__/' + self.user.encode('utf-8') + '/outbox/'
-            d = self._request(
-                OK, 'POST', uri,
-                Headers({
-                        'content-type': ['text/calendar'],
-                        'originator': [self.email],
-                        'recipient': ['mailto:' + email]}),
-                StringProducer(self._POST_AVAILABILITY % {
-                        'attendee': 'mailto:' + email,
-                        'organizer': self.email,
-                        'vfreebusy-uid': str(uuid4()).upper(),
-                        'event-uid': uid.encode('utf-8'),
-                        'start': dateTimeToString(start, convertToUTC=True),
-                        'end': dateTimeToString(end, convertToUTC=True),
-                        'now': dateTimeToString(now, convertToUTC=True)}))
-            d.addCallback(readBody)
+            return self.requestAvailability(
+                vevent.contents[u'vevent'][0].contents[u'dtstart'][0].value,
+                vevent.contents[u'vevent'][0].contents[u'dtend'][0].value,
+                [self.email, u'mailto:' + email],
+                [vevent.contents[u'vevent'][0].contents[u'uid'][0].value])
             return d
         d.addCallback(specific)
         def availability(ignored):
@@ -684,7 +669,79 @@
         return d
 
 
+    def requestAvailability(self, start, end, users, mask=set()):
+        """
+        Issue a VFREEBUSY request for I{roughly} the given date range for the
+        given users.  The date range is quantized to one day.  Because of this
+        it is an error for the range to span more than 24 hours.
 
+        @param start: A C{datetime} instance giving the beginning of the
+            desired range.
+
+        @param end: A C{datetime} instance giving the end of the desired range.
+
+        @param users: An iterable of user UUIDs which will be included in the
+            request.
+
+        @param mask: An iterable of event UIDs which are to be ignored for the
+            purposes of this availability lookup.
+
+        @return: A C{Deferred} which fires with a C{dict}.  Keys in the dict
+            are user UUIDs (those requested) and values are something else.
+        """
+        outbox = self.root + 'calendars/__uids__/%s/outbox/' % (
+            self.user.encode('utf-8'),)
+        headers = Headers()
+
+        if mask:
+            maskStr = u'\r\n'.join(['X-CALENDARSERVER-MASK-UID:' + uid
+                                    for uid in mask]) + u'\r\n'
+        else:
+            maskStr = u''
+        maskStr = maskStr.encode('utf-8')
+
+        attendeeStr = '\r\n'.join(['ATTENDEE:' + uuid.encode('utf-8')
+                                   for uuid in users]) + '\r\n'
+
+        # iCal issues 24 hour wide vfreebusy requests, starting and ending at 4am.
+        if start.date() != end.date():
+            raise RuntimeError("Cannot vfreebusy across multiple days")
+
+        start = start.replace(hour=0, minute=0, second=0, microsecond=0)
+        end = start + timedelta(hours=24)
+
+        start = dateTimeToString(start, convertToUTC=True)
+        end = dateTimeToString(end, convertToUTC=True)
+        now = dateTimeToString(datetime.now(), convertToUTC=True)
+
+        # XXX Why does it not end up UTC sometimes?
+        if not start.endswith('Z'):
+            start = start + 'Z'
+        if not end.endswith('Z'):
+            end = end + 'Z'
+        if not now.endswith('Z'):
+            now = now + 'Z'
+
+        d = self._request(
+            OK, 'POST', outbox,
+            Headers({
+                    'content-type': ['text/calendar'],
+                    'originator': [self.email],
+                    'recipient': [u', '.join(users).encode('utf-8')]}),
+            StringProducer(self._POST_AVAILABILITY % {
+                    'attendees': attendeeStr,
+                    'summary': (u'Availability for %s' % (', '.join(users),)).encode('utf-8'),
+                    'organizer': self.email.encode('utf-8'),
+                    'vfreebusy-uid': str(uuid4()).upper(),
+                    'event-mask': maskStr,
+                    'start': start,
+                    'end': end,
+                    'now': now}))
+        d.addCallback(readBody)
+        return d
+
+
+
 class RequestLogger(object):
     format = u"%(user)s request %(code)s%(success)s[%(duration)5.2f s] %(method)8s %(url)s"
     success = u"\N{CHECK MARK}"

Modified: CalendarServer/trunk/contrib/performance/loadtest/request-data/sl_post_availability.request
===================================================================
--- CalendarServer/trunk/contrib/performance/loadtest/request-data/sl_post_availability.request	2011-06-13 18:00:44 UTC (rev 7580)
+++ CalendarServer/trunk/contrib/performance/loadtest/request-data/sl_post_availability.request	2011-06-13 18:01:27 UTC (rev 7581)
@@ -6,11 +6,9 @@
 BEGIN:VFREEBUSY
 UID:%(vfreebusy-uid)s
 DTEND:%(end)s
-ATTENDEE:%(attendee)s
-DTSTART:%(start)s
-X-CALENDARSERVER-MASK-UID:%(event-uid)s
-DTSTAMP:%(now)s
+%(attendees)sDTSTART:%(start)s
+%(event-mask)sDTSTAMP:%(now)s
 ORGANIZER:%(organizer)s
-SUMMARY:Availability for %(attendee)s
+SUMMARY:%(summary)s
 END:VFREEBUSY
 END:VCALENDAR

Modified: CalendarServer/trunk/contrib/performance/loadtest/test_ical.py
===================================================================
--- CalendarServer/trunk/contrib/performance/loadtest/test_ical.py	2011-06-13 18:00:44 UTC (rev 7580)
+++ CalendarServer/trunk/contrib/performance/loadtest/test_ical.py	2011-06-13 18:01:27 UTC (rev 7581)
@@ -15,13 +15,16 @@
 #
 ##
 
+from datetime import datetime
+
 from vobject import readComponents
 from vobject.base import Component, ContentLine
+from vobject.icalendar import dateTimeToString
 
 from twisted.python.failure import Failure
 from twisted.internet.defer import Deferred
 from twisted.trial.unittest import TestCase
-from twisted.web.http import MULTI_STATUS
+from twisted.web.http import OK, MULTI_STATUS
 from twisted.web.client import ResponseDone
 from twisted.internet.protocol import ProtocolToConsumerAdapter
 
@@ -795,12 +798,26 @@
 """
 
 
+class MemoryResponse(object):
+    def __init__(self, version, code, phrase, headers, bodyProducer):
+        self.bodyProducer = bodyProducer
+        self.length = bodyProducer.length
+
+
+    def deliverBody(self, protocol):
+        protocol.makeConnection(self.bodyProducer)
+        d = self.bodyProducer.startProducing(ProtocolToConsumerAdapter(protocol))
+        d.addCallback(lambda ignored: protocol.connectionLost(Failure(ResponseDone())))
+
+
+
 class SnowLeopardMixin:
     """
     Mixin for L{TestCase}s for L{SnowLeopard}.
     """
     def setUp(self):
-        self.client = SnowLeopard(None, "127.0.0.1", 80, None, None)
+        self.user = "user91"
+        self.client = SnowLeopard(None, "127.0.0.1", 80, self.user, None)
 
 
     def interceptRequests(self):
@@ -1106,13 +1123,65 @@
 
 
 
-class MemoryResponse(object):
-    def __init__(self, version, code, phrase, headers, bodyProducer):
-        self.bodyProducer = bodyProducer
-        self.length = bodyProducer.length
+class VFreeBusyTests(SnowLeopardMixin, TestCase):
+    """
+    Tests for L{SnowLeopard.requestAvailability}.
+    """
+    def test_requestAvailability(self):
+        """
+        L{SnowLeopard.requestAvailability} accepts a date range and a set of
+        account uuids and issues a VFREEBUSY request.  It returns a Deferred
+        which fires with a dict mapping account uuids to availability range
+        information.
+        """
+        self.client.uuid = u'urn:uuid:user01'
+        self.client.email = u'mailto:user01 at example.com'
+        requests = self.interceptRequests()
 
+        start = datetime(2011, 6, 10, 10, 45, 0)
+        end = datetime(2011, 6, 10, 11, 15, 0)
+        d = self.client.requestAvailability(
+            start, end, [u"urn:uuid:user05", u"urn:uuid:user10"])
 
-    def deliverBody(self, protocol):
-        protocol.makeConnection(self.bodyProducer)
-        d = self.bodyProducer.startProducing(ProtocolToConsumerAdapter(protocol))
-        d.addCallback(lambda ignored: protocol.connectionLost(Failure(ResponseDone())))
+        result, req = requests.pop(0)
+        expectedResponseCode, method, url, headers, body = req
+
+        self.assertEqual(OK, expectedResponseCode)
+        self.assertEqual('POST', method)
+        self.assertEqual(
+            'http://127.0.0.1:80/calendars/__uids__/%s/outbox/' % (self.user,),
+            url)
+
+        self.assertEqual(headers.getRawHeaders('originator'), ['mailto:user01 at example.com'])
+        self.assertEqual(headers.getRawHeaders('recipient'), ['urn:uuid:user05, urn:uuid:user10'])
+        self.assertEqual(headers.getRawHeaders('content-type'), ['text/calendar'])
+
+        consumer = MemoryConsumer()
+        finished = body.startProducing(consumer)
+        def cbFinished(ignored):
+            vevent = list(readComponents(consumer.value()))[0]
+            uid = vevent.contents[u'vfreebusy'][0].contents[u'uid'][0].value.encode('utf-8')
+            dtstamp = vevent.contents[u'vfreebusy'][0].contents[u'dtstamp'][0].value
+            dtstamp = dateTimeToString(dtstamp, False)
+            self.assertEqual(
+"""\
+BEGIN:VCALENDAR
+CALSCALE:GREGORIAN
+VERSION:2.0
+METHOD:REQUEST
+PRODID:-//Apple Inc.//iCal 4.0.3//EN
+BEGIN:VFREEBUSY
+UID:%(uid)s
+DTEND:20110611T000000Z
+ATTENDEE:urn:uuid:user05
+ATTENDEE:urn:uuid:user10
+DTSTART:20110610T000000Z
+DTSTAMP:%(dtstamp)s
+ORGANIZER:mailto:user01 at example.com
+SUMMARY:Availability for urn:uuid:user05, urn:uuid:user10
+END:VFREEBUSY
+END:VCALENDAR
+""".replace('\n', '\r\n') % {'uid': uid, 'dtstamp': dtstamp},consumer.value())
+        finished.addCallback(cbFinished)
+        return finished
+
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.macosforge.org/pipermail/calendarserver-changes/attachments/20110613/42a7ac5b/attachment-0001.html>


More information about the calendarserver-changes mailing list