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

source_changes at macosforge.org source_changes at macosforge.org
Fri Jan 28 14:25:53 PST 2011


Revision: 6819
          http://trac.macosforge.org/projects/calendarserver/changeset/6819
Author:   exarkun at twistedmatrix.com
Date:     2011-01-28 14:25:52 -0800 (Fri, 28 Jan 2011)
Log Message:
-----------
Track schedule-tag; expose event change events to observers; add a method for changing an attendee on an event; track events globally and per-calendar.

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

Modified: CalendarServer/trunk/contrib/performance/loadtest/ical.py
===================================================================
--- CalendarServer/trunk/contrib/performance/loadtest/ical.py	2011-01-27 21:48:24 UTC (rev 6818)
+++ CalendarServer/trunk/contrib/performance/loadtest/ical.py	2011-01-28 22:25:52 UTC (rev 6819)
@@ -42,6 +42,8 @@
 from httpclient import StringProducer, readBody
 from httpauth import AuthHandlerAgent
 
+from subscribe import Periodical
+
 def loadRequestBody(label):
     return FilePath(__file__).sibling('request-data').child(label + '.request').getContent()
 
@@ -53,6 +55,7 @@
     def __init__(self, url, etag, vevent=None):
         self.url = url
         self.etag = etag
+        self.scheduleTag = None
         self.vevent = vevent
 
 
@@ -63,15 +66,24 @@
         self.name = name
         self.url = url
         self.ctag = ctag
+        self.events = {}
 
 
 
 class BaseClient(object):
+    user = None
+    _events = None
+    _calendars = None
+
     def addEventAttendee(self, href, attendee):
         raise NotImplementedError("%r does not implement addEventAttendee" % (self.__class__,))
 
 
+    def changeEventAttendee(self, href, oldAttendee, newAttendee):
+        raise NotImplementedError("%r does not implement changeEventAttendee" % (self.__class__,))
 
+
+
 class SnowLeopard(BaseClient):
     """
     Implementation of the SnowLeopard iCal network behavior.
@@ -86,7 +98,7 @@
 
     USER_AGENT = "DAVKit/4.0.3 (732); CalendarStore/4.0.3 (991); iCal/4.0.3 (1388); Mac OS X/10.6.4 (10F569)"
 
-    CALENDAR_HOME_POLL_INTERVAL = 15 * 60
+    CALENDAR_HOME_POLL_INTERVAL = 15 # * 60
 
     _STARTUP_PRINCIPAL_PROPFIND = loadRequestBody('sl_startup_principal_propfind')
     _STARTUP_PRINCIPALS_REPORT = loadRequestBody('sl_startup_principals_report')
@@ -118,7 +130,12 @@
         # part of), values are Event instances.
         self._events = {}
 
+        # Allow events to go out into the world.
+        self.catalog = {
+            "eventChanged": Periodical(),
+            }
 
+
     def _request(self, expectedResponseCode, method, url, headers=None, body=None):
         if headers is None:
             headers = Headers({})
@@ -128,12 +145,15 @@
         before = self.reactor.seconds()
         def report(response):
             success = response.code == expectedResponseCode
+            # if not success:
+            #     import pdb; pdb.set_trace()
             after = self.reactor.seconds()
             # XXX This is time to receive response headers, not time
             # to receive full response.  Should measure the latter, if
             # not both.
             msg(
                 type="response", success=success, method=method,
+                headers=headers, body=body,
                 duration=(after - before), url=url)
             return response
         d.addCallback(report)
@@ -253,8 +273,6 @@
             Headers({'content-type': ['text/xml'], 'depth': ['1']}),
             StringProducer(self._CALENDAR_PROPFIND))
 
-        # XXX Check the response status code
-
         body = yield readBody(response)
 
         result = self._parseMultiStatus(body)
@@ -269,24 +287,39 @@
                 continue
 
             if responseHref not in self._events:
-                self._events[responseHref] = Event(responseHref, None)
+                self._setEvent(responseHref, Event(responseHref, None))
                 
             event = self._events[responseHref]
             if event.etag != etag:
-                response = yield self._updateEvent(url, responseHref)
+                response = yield self._eventReport(url, responseHref)
                 body = yield readBody(response)
-                self.eventChanged(responseHref, body)
+                res = self._parseMultiStatus(body)[responseHref]
+                text = res.getTextProperties()
+                etag = text[davxml.getetag]
+                try:
+                    scheduleTag = text[caldavxml.schedule_tag]
+                except KeyError:
+                    scheduleTag = None
+                body = text[caldavxml.calendar_data]
+                self.eventChanged(responseHref, etag, scheduleTag, body)
 
 
-    def eventChanged(self, href, body):
+    def _setEvent(self, href, event):
+        self._events[href] = event
+        calendar, uid = href.rsplit('/', 1)
+        self._calendars[calendar + '/'].events[uid] = event
+
+
+    def eventChanged(self, href, etag, scheduleTag, body):
         event = self._events[href]
-        res = self._parseMultiStatus(body)[href]
-        text = res.getTextProperties()
-        event.etag = text[davxml.getetag]
-        event.vevent = list(readComponents(text[caldavxml.calendar_data]))[0]
+        event.etag = etag
+        if scheduleTag is not None:
+            event.scheduleTag = scheduleTag
+        event.vevent = list(readComponents(body))[0]
+        self.catalog["eventChanged"].issue(href)
 
                 
-    def _updateEvent(self, calendar, event):
+    def _eventReport(self, calendar, event):
         # Next do a REPORT on each event that might have information
         # we don't know about.
         return self._request(
@@ -465,18 +498,43 @@
                 StringProducer(vevent.serialize()))
             return d
         d.addCallback(availability)
-        def check(ignored):
-            # Finally, re-retrieve the event
-            return self._request(OK, 'GET', self.root + href[1:])
-        d.addCallback(check)
+        # Finally, re-retrieve the event to update the etag
+        d.addCallback(self._updateEvent, href)
+        return d
+
+
+    def changeEventAttendee(self, href, oldAttendee, newAttendee):
+        event = self._events[href]
+        vevent = event.vevent
+
+        # Change the event to have the new attendee instead of the old attendee
+        attendees = vevent.contents[u'vevent'][0].contents[u'attendee']
+        attendees.remove(oldAttendee)
+        attendees.append(newAttendee)
+        headers = Headers({
+                'content-type': ['text/calendar'],
+                })
+        if event.scheduleTag is not None:
+            headers.addRawHeader('if-schedule-tag-match', event.scheduleTag)
+
+        d = self._request(
+            NO_CONTENT, 'PUT', self.root + href[1:],
+            headers, StringProducer(vevent.serialize()))
+        d.addCallback(self._updateEvent, href)
+        return d
+
+
+    def _updateEvent(self, ignored, href):
+        d = self._request(OK, 'GET', self.root + href[1:])
         def getETag(response):
-            etag = response.headers.getRawHeaders('etag')[0]
-            return readBody(response).addCallback(lambda body: (etag, body))
+            headers = response.headers
+            etag = headers.getRawHeaders('etag')[0]
+            scheduleTag = headers.getRawHeaders('schedule-tag')[0]
+            return readBody(response).addCallback(
+                lambda body: (etag, scheduleTag, body))
         d.addCallback(getETag)
-        def record((etag, body)):
-            event = self._events[href]
-            event.etag = etag
-            event.vevent = list(readComponents(body))[0]
+        def record((etag, scheduleTag, body)):
+            self.eventChanged(href, etag, scheduleTag, body)
         d.addCallback(record)
         return d
 

Modified: CalendarServer/trunk/contrib/performance/loadtest/test_ical.py
===================================================================
--- CalendarServer/trunk/contrib/performance/loadtest/test_ical.py	2011-01-27 21:48:24 UTC (rev 6818)
+++ CalendarServer/trunk/contrib/performance/loadtest/test_ical.py	2011-01-28 22:25:52 UTC (rev 6819)
@@ -15,6 +15,10 @@
 #
 ##
 
+from vobject import readComponents
+from vobject.base import ContentLine
+
+from twisted.internet.defer import Deferred
 from twisted.trial.unittest import TestCase
 
 from protocol.url import URL
@@ -22,7 +26,8 @@
 from protocol.caldav.definitions import caldavxml
 from protocol.caldav.definitions import csxml
 
-from ical import SnowLeopard
+from loadtest.ical import Event, SnowLeopard
+from httpclient import MemoryConsumer
 
 
 PRINCIPAL_PROPFIND_RESPONSE = """\
@@ -719,9 +724,47 @@
 </multistatus>
 """
 
+EVENT = """\
+BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//Apple Inc.//iCal 4.0.3//EN
+CALSCALE:GREGORIAN
+BEGIN:VTIMEZONE
+TZID:America/New_York
+BEGIN:DAYLIGHT
+TZOFFSETFROM:-0500
+RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=2SU
+DTSTART:20070311T020000
+TZNAME:EDT
+TZOFFSETTO:-0400
+END:DAYLIGHT
+BEGIN:STANDARD
+TZOFFSETFROM:-0400
+RRULE:FREQ=YEARLY;BYMONTH=11;BYDAY=1SU
+DTSTART:20071104T020000
+TZNAME:EST
+TZOFFSETTO:-0500
+END:STANDARD
+END:VTIMEZONE
+BEGIN:VEVENT
+CREATED:20101018T155454Z
+UID:D94F247D-7433-43AF-B84B-ADD684D023B0
+DTEND;TZID=America/New_York:20101028T130000
+ATTENDEE;CN="User 03";CUTYPE=INDIVIDUAL;EMAIL="user03 at example.com";PARTS
+ TAT=NEEDS-ACTION;ROLE=REQ-PARTICIPANT;RSVP=TRUE:mailto:user03 at example.co
+ m
+ATTENDEE;CN="User 01";CUTYPE=INDIVIDUAL;PARTSTAT=ACCEPTED:mailto:user01@
+ example.com
+TRANSP:OPAQUE
+SUMMARY:Attended Event
+DTSTART;TZID=America/New_York:20101028T120000
+DTSTAMP:20101018T155513Z
+ORGANIZER;CN="User 01":mailto:user01 at example.com
+SEQUENCE:3
+END:VEVENT
+END:VCALENDAR
+"""
 
-
-
 class SnowLeopardTests(TestCase):
     """
     Tests for L{SnowLeopard}.
@@ -801,3 +844,44 @@
         self.assertEquals(outbox.name, None)
         self.assertEquals(outbox.url, "/calendars/__uids__/user01/outbox/")
         self.assertEquals(outbox.ctag, None)
+
+
+    def test_changeEventAttendee(self):
+        """
+        SnowLeopard.changeEventAttendee removes one attendee from an
+        existing event and appends another.
+        """
+        requests = []
+        def request(*args):
+            result = Deferred()
+            requests.append((result, args))
+            return result
+        self.client._request = request
+
+        vevent = list(readComponents(EVENT))[0]
+        attendees = vevent.contents[u'vevent'][0].contents[u'attendee']
+        old = attendees[0]
+        new = ContentLine.duplicate(old)
+        new.params[u'CN'] = [u'Some Other Guy']
+        event = Event('/some/calendar/1234.ics', None, vevent)
+        self.client._events[event.url] = event
+        self.client.changeEventAttendee(event.url, old, new)
+
+        result, req = requests.pop(0)
+
+        # iCal PUTs the new VCALENDAR object.
+        expectedResponseCode, method, url, headers, body = req
+        self.assertEquals(method, 'PUT')
+        self.assertEquals(url, 'http://127.0.0.1:80' + event.url)
+        self.assertEquals(headers.getRawHeaders('content-type'), ['text/calendar'])
+
+        consumer = MemoryConsumer()
+        finished = body.startProducing(consumer)
+        def cbFinished(ignored):
+            vevent = list(readComponents(consumer.value()))[0]
+            attendees = vevent.contents[u'vevent'][0].contents[u'attendee']
+            self.assertEquals(len(attendees), 2)
+            self.assertEquals(attendees[0].params[u'CN'], [u'User 01'])
+            self.assertEquals(attendees[1].params[u'CN'], [u'Some Other Guy'])
+        finished.addCallback(cbFinished)
+        return finished
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.macosforge.org/pipermail/calendarserver-changes/attachments/20110128/21336265/attachment-0001.html>


More information about the calendarserver-changes mailing list