[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