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

source_changes at macosforge.org source_changes at macosforge.org
Tue Apr 10 08:41:35 PDT 2012


Revision: 9004
          http://trac.macosforge.org/projects/calendarserver/changeset/9004
Author:   cdaboo at apple.com
Date:     2012-04-10 08:41:35 -0700 (Tue, 10 Apr 2012)
Log Message:
-----------
Significant re-factoring of loadsim to support multiple client types, new client profiles, better reporting, and various fixes.

Modified Paths:
--------------
    CalendarServer/trunk/contrib/performance/loadtest/config.dist.plist
    CalendarServer/trunk/contrib/performance/loadtest/config.plist
    CalendarServer/trunk/contrib/performance/loadtest/ical.py
    CalendarServer/trunk/contrib/performance/loadtest/logger.py
    CalendarServer/trunk/contrib/performance/loadtest/population.py
    CalendarServer/trunk/contrib/performance/loadtest/profiles.py
    CalendarServer/trunk/contrib/performance/loadtest/sim.py
    CalendarServer/trunk/contrib/performance/loadtest/test_ical.py
    CalendarServer/trunk/contrib/performance/loadtest/test_population.py
    CalendarServer/trunk/contrib/performance/loadtest/test_profiles.py
    CalendarServer/trunk/contrib/performance/loadtest/test_sim.py
    CalendarServer/trunk/contrib/performance/loadtest/test_trafficlogger.py

Added Paths:
-----------
    CalendarServer/trunk/contrib/performance/loadtest/request-data/OS_X_10_6/
    CalendarServer/trunk/contrib/performance/loadtest/request-data/OS_X_10_6/poll_calendar_multiget.request
    CalendarServer/trunk/contrib/performance/loadtest/request-data/OS_X_10_6/poll_calendar_multiget_hrefs.request
    CalendarServer/trunk/contrib/performance/loadtest/request-data/OS_X_10_6/poll_calendar_propfind.request
    CalendarServer/trunk/contrib/performance/loadtest/request-data/OS_X_10_6/poll_calendar_propfind_d1.request
    CalendarServer/trunk/contrib/performance/loadtest/request-data/OS_X_10_6/poll_calendarhome_propfind.request
    CalendarServer/trunk/contrib/performance/loadtest/request-data/OS_X_10_6/poll_notification_propfind_d1.request
    CalendarServer/trunk/contrib/performance/loadtest/request-data/OS_X_10_6/post_availability.request
    CalendarServer/trunk/contrib/performance/loadtest/request-data/OS_X_10_6/startup_calendar_color_proppatch.request
    CalendarServer/trunk/contrib/performance/loadtest/request-data/OS_X_10_6/startup_calendar_order_proppatch.request
    CalendarServer/trunk/contrib/performance/loadtest/request-data/OS_X_10_6/startup_calendar_timezone_proppatch.request
    CalendarServer/trunk/contrib/performance/loadtest/request-data/OS_X_10_6/startup_notification_propfind.request
    CalendarServer/trunk/contrib/performance/loadtest/request-data/OS_X_10_6/startup_principal_expand.request
    CalendarServer/trunk/contrib/performance/loadtest/request-data/OS_X_10_6/startup_principal_propfind.request
    CalendarServer/trunk/contrib/performance/loadtest/request-data/OS_X_10_6/startup_principal_propfind_initial.request
    CalendarServer/trunk/contrib/performance/loadtest/request-data/OS_X_10_6/startup_principals_report.request
    CalendarServer/trunk/contrib/performance/loadtest/request-data/OS_X_10_6/startup_well_known.request
    CalendarServer/trunk/contrib/performance/loadtest/request-data/OS_X_10_6/user_list_principal_property_search.request
    CalendarServer/trunk/contrib/performance/loadtest/request-data/OS_X_10_7/
    CalendarServer/trunk/contrib/performance/loadtest/request-data/OS_X_10_7/Profile
    CalendarServer/trunk/contrib/performance/loadtest/request-data/OS_X_10_7/poll_calendar_multiget.request
    CalendarServer/trunk/contrib/performance/loadtest/request-data/OS_X_10_7/poll_calendar_multiget_hrefs.request
    CalendarServer/trunk/contrib/performance/loadtest/request-data/OS_X_10_7/poll_calendar_propfind.request
    CalendarServer/trunk/contrib/performance/loadtest/request-data/OS_X_10_7/poll_calendar_propfind_d1.request
    CalendarServer/trunk/contrib/performance/loadtest/request-data/OS_X_10_7/poll_calendar_sync.request
    CalendarServer/trunk/contrib/performance/loadtest/request-data/OS_X_10_7/poll_calendarhome_propfind.request
    CalendarServer/trunk/contrib/performance/loadtest/request-data/OS_X_10_7/poll_notification_propfind_d1.request
    CalendarServer/trunk/contrib/performance/loadtest/request-data/OS_X_10_7/post_availability.request
    CalendarServer/trunk/contrib/performance/loadtest/request-data/OS_X_10_7/startup_calendar_color_proppatch.request
    CalendarServer/trunk/contrib/performance/loadtest/request-data/OS_X_10_7/startup_calendar_order_proppatch.request
    CalendarServer/trunk/contrib/performance/loadtest/request-data/OS_X_10_7/startup_calendar_timezone_proppatch.request
    CalendarServer/trunk/contrib/performance/loadtest/request-data/OS_X_10_7/startup_delegate_principal_propfind.request
    CalendarServer/trunk/contrib/performance/loadtest/request-data/OS_X_10_7/startup_principal_expand.request
    CalendarServer/trunk/contrib/performance/loadtest/request-data/OS_X_10_7/startup_principal_propfind.request
    CalendarServer/trunk/contrib/performance/loadtest/request-data/OS_X_10_7/startup_principal_propfind_initial.request
    CalendarServer/trunk/contrib/performance/loadtest/request-data/OS_X_10_7/startup_principals_report.request
    CalendarServer/trunk/contrib/performance/loadtest/request-data/OS_X_10_7/startup_well_known.request
    CalendarServer/trunk/contrib/performance/loadtest/request-data/OS_X_10_7/user_list_principal_property_search.request
    CalendarServer/trunk/contrib/performance/loadtest/request-data/iOS_5/
    CalendarServer/trunk/contrib/performance/loadtest/request-data/iOS_5/Profile
    CalendarServer/trunk/contrib/performance/loadtest/request-data/iOS_5/poll_calendar_multiget.request
    CalendarServer/trunk/contrib/performance/loadtest/request-data/iOS_5/poll_calendar_multiget_hrefs.request
    CalendarServer/trunk/contrib/performance/loadtest/request-data/iOS_5/poll_calendar_propfind.request
    CalendarServer/trunk/contrib/performance/loadtest/request-data/iOS_5/poll_calendar_propfind_d1.request
    CalendarServer/trunk/contrib/performance/loadtest/request-data/iOS_5/poll_calendar_vevent_tr_query.request
    CalendarServer/trunk/contrib/performance/loadtest/request-data/iOS_5/poll_calendar_vtodo_query.request
    CalendarServer/trunk/contrib/performance/loadtest/request-data/iOS_5/poll_calendarhome_propfind.request
    CalendarServer/trunk/contrib/performance/loadtest/request-data/iOS_5/startup_calendar_color_proppatch.request
    CalendarServer/trunk/contrib/performance/loadtest/request-data/iOS_5/startup_calendar_order_proppatch.request
    CalendarServer/trunk/contrib/performance/loadtest/request-data/iOS_5/startup_principal_propfind.request
    CalendarServer/trunk/contrib/performance/loadtest/request-data/iOS_5/startup_principal_propfind_initial.request
    CalendarServer/trunk/contrib/performance/loadtest/request-data/iOS_5/startup_principals_report.request
    CalendarServer/trunk/contrib/performance/loadtest/request-data/iOS_5/startup_well_known.request

Removed Paths:
-------------
    CalendarServer/trunk/contrib/performance/loadtest/request-data/sl_calendar_propfind.request
    CalendarServer/trunk/contrib/performance/loadtest/request-data/sl_calendar_report.request
    CalendarServer/trunk/contrib/performance/loadtest/request-data/sl_calendar_report_href.request
    CalendarServer/trunk/contrib/performance/loadtest/request-data/sl_post_availability.request
    CalendarServer/trunk/contrib/performance/loadtest/request-data/sl_startup_calendarhome_propfind.request
    CalendarServer/trunk/contrib/performance/loadtest/request-data/sl_startup_notification_propfind.request
    CalendarServer/trunk/contrib/performance/loadtest/request-data/sl_startup_principal_propfind.request
    CalendarServer/trunk/contrib/performance/loadtest/request-data/sl_startup_principal_propfind_initial.request
    CalendarServer/trunk/contrib/performance/loadtest/request-data/sl_startup_principal_report.request
    CalendarServer/trunk/contrib/performance/loadtest/request-data/sl_startup_principals_report.request
    CalendarServer/trunk/contrib/performance/loadtest/request-data/sl_user_list_principal_property_search.request

Modified: CalendarServer/trunk/contrib/performance/loadtest/config.dist.plist
===================================================================
--- CalendarServer/trunk/contrib/performance/loadtest/config.dist.plist	2012-04-10 00:41:41 UTC (rev 9003)
+++ CalendarServer/trunk/contrib/performance/loadtest/config.dist.plist	2012-04-10 15:41:35 UTC (rev 9004)
@@ -86,6 +86,11 @@
 				<!-- Number of seconds between the introduction of each group. -->
 				<key>interval</key>
 				<integer>3</integer>
+
+				<!-- Number of clients each user is assigned to. -->
+				<!-- Set weight of clients to 1 if this is > 1. Number of clients must match this value if > 1. -->
+				<key>clientsPerUser</key>
+				<integer>1</integer>
 			</dict>
 
 		</dict>
@@ -102,17 +107,17 @@
 
 				<!-- Here is a Snow Leopard iCal simulator. -->
 				<key>software</key>
-				<string>contrib.performance.loadtest.ical.SnowLeopard</string>
+				<string>contrib.performance.loadtest.ical.OS_X_10_6</string>
 
-				<!-- Arguments to use to initialize the SnowLeopard instance. -->
+				<!-- Arguments to use to initialize the OS_X_10_6 instance. -->
 				<key>params</key>
 				<dict>
-					<!-- SnowLeopard can poll the calendar home at some interval. This is 
+					<!-- OS_X_10_6 can poll the calendar home at some interval. This is 
 						in seconds. -->
 					<key>calendarHomePollInterval</key>
 					<integer>30</integer>
 
-					<!-- If the server advertises xmpp push, SnowLeopard can wait for notifications 
+					<!-- If the server advertises xmpp push, OS_X_10_6 can wait for notifications 
 						about calendar home changes instead of polling for them periodically. If 
 						this option is true, then look for the server advertisement for xmpp push 
 						and use it if possible. Still fall back to polling if there is no xmpp push 

Modified: CalendarServer/trunk/contrib/performance/loadtest/config.plist
===================================================================
--- CalendarServer/trunk/contrib/performance/loadtest/config.plist	2012-04-10 00:41:41 UTC (rev 9003)
+++ CalendarServer/trunk/contrib/performance/loadtest/config.plist	2012-04-10 15:41:35 UTC (rev 9004)
@@ -74,6 +74,11 @@
 				<!-- Number of seconds between the introduction of each group. -->
 				<key>interval</key>
 				<integer>3</integer>
+
+				<!-- Number of clients each user is assigned to. -->
+				<!-- Set weight of clients to 1 if this is > 1. Number of clients must match this value if > 1. -->
+				<key>clientsPerUser</key>
+				<integer>1</integer>
 			</dict>
 
 		</dict>
@@ -90,17 +95,17 @@
 
 				<!-- Here is a Snow Leopard iCal simulator. -->
 				<key>software</key>
-				<string>contrib.performance.loadtest.ical.SnowLeopard</string>
+				<string>contrib.performance.loadtest.ical.OS_X_10_6</string>
 
-				<!-- Arguments to use to initialize the SnowLeopard instance. -->
+				<!-- Arguments to use to initialize the OS_X_10_6 instance. -->
 				<key>params</key>
 				<dict>
-					<!-- SnowLeopard can poll the calendar home at some interval. This is 
+					<!-- OS_X_10_6 can poll the calendar home at some interval. This is 
 						in seconds. -->
 					<key>calendarHomePollInterval</key>
 					<integer>30</integer>
 
-					<!-- If the server advertises xmpp push, SnowLeopard can wait for notifications 
+					<!-- If the server advertises xmpp push, OS_X_10_6 can wait for notifications 
 						about calendar home changes instead of polling for them periodically. If 
 						this option is true, then look for the server advertisement for xmpp push 
 						and use it if possible. Still fall back to polling if there is no xmpp push 

Modified: CalendarServer/trunk/contrib/performance/loadtest/ical.py
===================================================================
--- CalendarServer/trunk/contrib/performance/loadtest/ical.py	2012-04-10 00:41:41 UTC (rev 9003)
+++ CalendarServer/trunk/contrib/performance/loadtest/ical.py	2012-04-10 15:41:35 UTC (rev 9004)
@@ -1,5 +1,5 @@
 ##
-# Copyright (c) 2010 Apple Inc. All rights reserved.
+# Copyright (c) 2010-2012 Apple Inc. All rights reserved.
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
 # you may not use this file except in compliance with the License.
@@ -24,13 +24,15 @@
 from pycalendar.duration import PyCalendarDuration
 from pycalendar.timezone import PyCalendarTimezone
 from pycalendar.datetime import PyCalendarDateTime
+from twext.web2.responsecode import MOVED_PERMANENTLY
+
 ElementTree.QName.__repr__ = lambda self: '<QName %r>' % (self.text,)
 
 from twisted.python.log import addObserver, err, msg
 from twisted.python.filepath import FilePath
-from twisted.python.failure import Failure
 from twisted.python.util import FancyEqMixin
-from twisted.internet.defer import Deferred, inlineCallbacks, returnValue
+from twisted.internet.defer import Deferred, inlineCallbacks, returnValue,\
+    succeed
 from twisted.internet.task import LoopingCall
 from twisted.web.http_headers import Headers
 from twisted.web.http import OK, MULTI_STATUS, CREATED, NO_CONTENT, PRECONDITION_FAILED
@@ -49,8 +51,8 @@
 from contrib.performance.loadtest.subscribe import Periodical
 
 
-def loadRequestBody(label):
-    return FilePath(__file__).sibling('request-data').child(label + '.request').getContent()
+def loadRequestBody(clientType, label):
+    return FilePath(__file__).sibling('request-data').child(clientType).child(label + '.request').getContent()
 
 
 SUPPORTED_REPORT_SET = '{DAV:}supported-report-set'
@@ -104,48 +106,88 @@
 
 
 class Calendar(object):
-    def __init__(self, resourceType, componentTypes, name, url, ctag):
+    def __init__(self, resourceType, componentTypes, name, url, changeToken):
         self.resourceType = resourceType
         self.componentTypes = componentTypes
         self.name = name
         self.url = url
-        self.ctag = ctag
+        self.changeToken = changeToken
         self.events = {}
 
 
 
 class BaseClient(object):
-    user = None
-    _events = None
-    _calendars = None
+    """
+    Base interface for all simulated clients.
+    """
 
+    user = None         # User account details
+    _events = None      # Cache of events keyed by href
+    _calendars = None   # Cache of calendars keyed by href
+    started = False     # Whether or not startup() has been executed
+    _client_type = None # Type of this client used in logging 
+    _client_id = None   # Unique id for the client itself
+
+
     def _setEvent(self, href, event):
+        """
+        Cache the provided event
+        """
         self._events[href] = event
         calendar, uid = href.rsplit('/', 1)
         self._calendars[calendar + '/'].events[uid] = event
 
 
+    def _removeEvent(self, href):
+        """
+        Remove event from local cache.
+        """
+        del self._events[href]
+        calendar, uid = href.rsplit('/', 1)
+        del self._calendars[calendar + '/'].events[uid]
+
+
     def addEvent(self, href, vcalendar):
+        """
+        Called when a profile needs to add an event (no scheduling).
+        """
         raise NotImplementedError("%r does not implement addEvent" % (self.__class__,))
 
 
     def addInvite(self, href, vcalendar):
+        """
+        Called when a profile needs to add a new invite. The iCalendar data will already
+        contain ATTENDEEs.
+        """
         raise NotImplementedError("%r does not implement addInvite" % (self.__class__,))
 
 
     def deleteEvent(self, href):
+        """
+        Called when a profile needs to delete an event.
+        """
         raise NotImplementedError("%r does not implement deleteEvent" % (self.__class__,))
 
 
     def addEventAttendee(self, href, attendee):
+        """
+        Called when a profile needs to add an attendee to an existing event.
+        """
         raise NotImplementedError("%r does not implement addEventAttendee" % (self.__class__,))
 
 
     def changeEventAttendee(self, href, oldAttendee, newAttendee):
+        """
+        Called when a profile needs to change an attendee on an existing event.
+        Used when an attendee is accepting.
+        """
         raise NotImplementedError("%r does not implement changeEventAttendee" % (self.__class__,))
 
 
 class _PubSubClientFactory(PubSubClientFactory):
+    """
+    Factory for XMPP pubsub functionality.
+    """
     def __init__(self, client, *args, **kwargs):
         PubSubClientFactory.__init__(self, *args, **kwargs)
         self._client = client
@@ -167,45 +209,59 @@
 
 
 
-class SnowLeopard(BaseClient):
+class BaseAppleClient(BaseClient):
     """
-    Implementation of the SnowLeopard iCal network behavior.
-
-    Anything SnowLeopard iCal does on its own, or any particular
-    network behaviors it takes in response to a user action, belong on
-    this class.
-
-    Usage-profile based behaviors ("the user modifies an event every
-    3.2 minutes") belong elsewhere.
+    Implementation of common OS X/iOS client behavior.
     """
 
-    USER_AGENT = "DAVKit/4.0.3 (732); CalendarStore/4.0.3 (991); iCal/4.0.3 (1388); Mac OS X/10.6.4 (10F569)"
+    USER_AGENT = None   # Override this for specific clients
 
     # The default interval, used if none is specified in external
-    # configuration.  This is also the actual value used by Snow
-    # Leopard iCal.
+    # configuration.
     CALENDAR_HOME_POLL_INTERVAL = 15 * 60
     
     # The maximum number of resources to retrieve in a single multiget
     MULTIGET_BATCH_SIZE = 200
 
-    _STARTUP_PRINCIPAL_PROPFIND_INITIAL = loadRequestBody('sl_startup_principal_propfind_initial')
-    _STARTUP_PRINCIPAL_PROPFIND = loadRequestBody('sl_startup_principal_propfind')
-    _STARTUP_PRINCIPALS_REPORT = loadRequestBody('sl_startup_principals_report')
-    _STARTUP_CALENDARHOME_PROPFIND = loadRequestBody('sl_startup_calendarhome_propfind')
-    _STARTUP_NOTIFICATION_PROPFIND = loadRequestBody('sl_startup_notification_propfind')
-    _STARTUP_PRINCIPAL_REPORT = loadRequestBody('sl_startup_principal_report')
+    # Override and turn on if client supports Sync REPORT
+    _SYNC_REPORT = False
 
-    _CALENDAR_PROPFIND = loadRequestBody('sl_calendar_propfind')
-    _CALENDAR_REPORT = loadRequestBody('sl_calendar_report')
-    _CALENDAR_REPORT_HREF = loadRequestBody('sl_calendar_report_href')
+    # Override and turn on if client syncs using time-range queries
+    _SYNC_TIMERANGE = False
 
-    _USER_LIST_PRINCIPAL_PROPERTY_SEARCH = loadRequestBody('sl_user_list_principal_property_search')
-    _POST_AVAILABILITY = loadRequestBody('sl_post_availability')
+    # Override and turn off if client does not support attendee lookups
+    _ATTENDEE_LOOKUPS = True
 
+    # Request body data
+    _LOAD_PATH = None
+
+    _STARTUP_WELL_KNOWN = None
+    _STARTUP_PRINCIPAL_PROPFIND_INITIAL = None
+    _STARTUP_PRINCIPAL_PROPFIND = None
+    _STARTUP_PRINCIPALS_REPORT = None
+    _STARTUP_PRINCIPAL_EXPAND = None
+    _STARTUP_PROPPATCH_CALENDAR_COLOR = None
+    _STARTUP_PROPPATCH_CALENDAR_ORDER = None
+    _STARTUP_PROPPATCH_CALENDAR_TIMEZONE = None
+
+    _POLL_CALENDARHOME_PROPFIND = None
+    _POLL_CALENDAR_PROPFIND = None
+    _POLL_CALENDAR_PROPFIND_D1 = None
+    _POLL_CALENDAR_MULTIGET_REPORT = None
+    _POLL_CALENDAR_MULTIGET_REPORT_HREF = None
+    _POLL_CALENDAR_SYNC_REPORT = None
+    _POLL_NOTIFICATION_PROPFIND = None
+    _POLL_NOTIFICATION_PROPFIND_D1 = None
+
+    _USER_LIST_PRINCIPAL_PROPERTY_SEARCH = None
+    _POST_AVAILABILITY = None
+
     email = None
 
     def __init__(self, reactor, root, record, auth, calendarHomePollInterval=None, supportPush=True):
+        
+        self._client_id = str(uuid4())
+
         self.reactor = reactor
         self.agent = AuthHandlerAgent(Agent(self.reactor), auth)
         self.root = root
@@ -216,6 +272,8 @@
         self.calendarHomePollInterval = calendarHomePollInterval
 
         self.supportPush = supportPush
+        
+        self.supportSync = self._SYNC_REPORT
 
         # Keep track of the calendars on this account, keys are
         # Calendar URIs, values are Calendar instances.
@@ -241,40 +299,67 @@
             }
 
 
-    def _request(self, expectedResponseCodes, method, url, headers=None, body=None):
+    def _addDefaultHeaders(self, headers):
+        """
+        Add the clients default set of headers to ones being used in a request.
+        Default is to add User-Agent, sub-classes should override to add other
+        client specific things, Accept etc.
+        """
+        headers.setRawHeaders('User-Agent', [self.USER_AGENT])
+        
+    @inlineCallbacks
+    def _request(self, expectedResponseCodes, method, url, headers=None, body=None, method_label=None):
+        """
+        Execute a request and check against the expected response codes.
+        """
         if type(expectedResponseCodes) is int:
             expectedResponseCodes = (expectedResponseCodes,)
         if headers is None:
             headers = Headers({})
-        headers.setRawHeaders('User-Agent', [self.USER_AGENT])
-        msg(type="request", method=method, url=url, user=self.record.uid)
-        d = self.agent.request(method, url, headers, body)
+        self._addDefaultHeaders(headers)
+        msg(
+            type="request",
+            method=method_label if method_label else method,
+            url=url,
+            user=self.record.uid,
+            client_type=self._client_type,
+            client_id=self._client_id,
+        )
+
         before = self.reactor.seconds()
-        def report(response):
-            # XXX This is time to receive response headers, not time
-            # to receive full response.  Should measure the latter, if
-            # not both.
-            after = self.reactor.seconds()
+        response = yield self.agent.request(method, url, headers, body)
 
-            success = response.code in expectedResponseCodes
+        # XXX This is time to receive response headers, not time
+        # to receive full response.  Should measure the latter, if
+        # not both.
+        after = self.reactor.seconds()
 
-            # if not success:
-            #     import pdb; pdb.set_trace()
-            msg(
-                type="response", success=success, method=method,
-                headers=headers, body=body, code=response.code,
-                user=self.record.uid, duration=(after - before), url=url)
+        success = response.code in expectedResponseCodes
 
-            if success:
-                return response
-            raise IncorrectResponseCode(expectedResponseCodes, response)
-        d.addCallback(report)
-        return d
+        msg(
+            type="response",
+            success=success,
+            method=method_label if method_label else method,
+            headers=headers,
+            body=body,
+            code=response.code,
+            user=self.record.uid,
+            client_type=self._client_type,
+            client_id=self._client_id,
+            duration=(after - before),
+            url=url,
+        )
 
+        if success:
+            returnValue(response)
 
-    def _parseMultiStatus(self, response):
+        raise IncorrectResponseCode(expectedResponseCodes, response)
+
+
+    def _parseMultiStatus(self, response, otherTokens=False):
         """
-        Parse a <multistatus>
+        Parse a <multistatus> - might need to return other top-level elements
+        in the response - e.g. DAV:sync-token
         I{PROPFIND} request for the principal URL.
 
         @type response: C{str}
@@ -282,18 +367,160 @@
         """
         parser = PropFindParser()
         parser.parseData(response)
-        return parser.getResults()
+        if otherTokens:
+            return (parser.getResults(), parser.getOthers(),)
+        else:
+            return parser.getResults()
 
     
     _CALENDAR_TYPES = set([
             caldavxml.calendar,
             caldavxml.schedule_inbox,
-            caldavxml.schedule_outbox,
-            csxml.notification,
-            csxml.dropbox_home,
             ])
-    def _extractCalendars(self, response, calendarHome=None):
+
+    @inlineCallbacks
+    def _propfind(self, url, body, depth='0', allowedStatus=(MULTI_STATUS,), method_label=None):
         """
+        Issue a PROPFIND on the chosen URL
+        """
+        hdrs = Headers({'content-type': ['text/xml']})
+        if depth is not None:
+            hdrs.addRawHeader('depth', depth)
+        response = yield self._request(
+            allowedStatus,
+            'PROPFIND',
+            self.root + url[1:].encode('utf-8'),
+            hdrs,
+            StringProducer(body),
+            method_label=method_label,
+        )
+        body = yield readBody(response)
+        result = self._parseMultiStatus(body)
+        returnValue(result)
+
+
+    @inlineCallbacks
+    def _proppatch(self, url, body):
+        """
+        Issue a PROPPATCH on the chosen URL
+        """
+        hdrs = Headers({'content-type': ['text/xml']})
+        response = yield self._request(
+            (OK, MULTI_STATUS,),
+            'PROPPATCH',
+            self.root + url[1:].encode('utf-8'),
+            hdrs,
+            StringProducer(body)
+        )
+        if response.code == MULTI_STATUS:
+            body = yield readBody(response)
+            result = self._parseMultiStatus(body)
+            returnValue(result)
+        else:
+            returnValue(None)
+
+
+    @inlineCallbacks
+    def _report(self, url, body, depth='0', allowedStatus=(MULTI_STATUS,), otherTokens=False, method_label=None):
+        """
+        Issue a REPORT on the chosen URL
+        """
+        hdrs = Headers({'content-type': ['text/xml']})
+        if depth is not None:
+            hdrs.addRawHeader('depth', depth)
+        response = yield self._request(
+            allowedStatus,
+            'REPORT',
+            self.root + url[1:].encode('utf-8'),
+            hdrs,
+            StringProducer(body),
+            method_label=method_label,
+        )
+        body = yield readBody(response)
+        result = self._parseMultiStatus(body, otherTokens)
+        returnValue(result)
+
+
+    def _startupPropfindWellKnown(self):
+        """
+        Issue a PROPFIND on the /.well-known/caldav/ URL
+        """
+        return self._propfind(
+            '/.well-known/caldav/',
+            self._STARTUP_WELL_KNOWN,
+            allowedStatus=(MULTI_STATUS, MOVED_PERMANENTLY),
+        )
+
+
+    def _startupPropfindRoot(self):
+        """
+        Issue a PROPFIND on the / URL
+        """
+        return self._propfind(
+            '/',
+            self._STARTUP_WELL_KNOWN,
+        )
+
+
+    @inlineCallbacks
+    def _principalPropfindInitial(self, user):
+        """
+        Issue a PROPFIND on the /principals/users/<uid> URL to retrieve
+        the /principals/__uids__/<guid> principal URL
+        """
+        principalURL = '/principals/users/' + user + '/'
+        result = yield self._propfind(
+            '/principals/users/' + user + '/',
+            self._STARTUP_PRINCIPAL_PROPFIND_INITIAL,
+        )
+        returnValue(result[principalURL])
+
+
+    @inlineCallbacks
+    def _principalPropfind(self):
+        """
+        Issue a PROPFIND on the likely principal URL for the given
+        user and return a L{Principal} instance constructed from the
+        response.
+        """
+        result = yield self._propfind(
+            self.principalURL,
+            self._STARTUP_PRINCIPAL_PROPFIND,
+        )
+        returnValue(result[self.principalURL])
+
+
+    def _principalSearchPropertySetReport(self, principalCollectionSet):
+        """
+        Issue a principal-search-property-set REPORT against the chosen URL
+        """
+        return self._report(
+            principalCollectionSet,
+            self._STARTUP_PRINCIPALS_REPORT,
+            allowedStatus=(OK,),
+            method_label="REPORT{pset}",
+        )
+
+
+    @inlineCallbacks
+    def _calendarHomePropfind(self, calendarHomeSet):
+        """
+        Do the poll Depth:1 PROPFIND on the calendar home.
+        """
+        if not calendarHomeSet.endswith('/'):
+            calendarHomeSet = calendarHomeSet + '/'
+        result = yield self._propfind(
+            calendarHomeSet,
+            self._POLL_CALENDARHOME_PROPFIND,
+            depth='1',
+            method_label="PROPFIND{home}",
+        )
+        calendars = self._extractCalendars(result, calendarHomeSet)
+        returnValue((calendars, result,))
+
+
+    def _extractCalendars(self, results, calendarHome=None):
+        """
         Parse a calendar home PROPFIND response and create local state
         representing the calendars it contains.
 
@@ -301,14 +528,10 @@
         that from the response.
         """
         calendars = []
-        principals = self._parseMultiStatus(response)
+        for href in results:
 
-        # XXX Here, it would be really great to somehow use
-        # CalDAVClientLibrary.client.principal.CalDAVPrincipal.listCalendars
-        for principal in principals:
-
-            if principal == calendarHome:
-                text = principals[principal].getTextProperties()
+            if href == calendarHome:
+                text = results[href].getTextProperties()
                 try:
                     server = text[csxml.xmpp_server]
                     uri = text[csxml.xmpp_uri]
@@ -317,123 +540,81 @@
                     pass
                 else:
                     if server and uri:
-                        self.xmpp[principal] = XMPPPush(server, uri, pushkey)
+                        self.xmpp[href] = XMPPPush(server, uri, pushkey)
 
-            nodes = principals[principal].getNodeProperties()
+            nodes = results[href].getNodeProperties()
             for nodeType in nodes[davxml.resourcetype].getchildren():
                 if nodeType.tag in self._CALENDAR_TYPES:
-                    textProps = principals[principal].getTextProperties()
+                    textProps = results[href].getTextProperties()
                     componentTypes = set()
                     if nodeType.tag == caldavxml.calendar:
                         if caldavxml.supported_calendar_component_set in nodes:
                             for comp in nodes[caldavxml.supported_calendar_component_set].getchildren():
                                 componentTypes.add(comp.get("name").upper())
+                    
+                    changeTag = davxml.sync_token if self.supportSync else csxml.getctag
                     calendars.append(Calendar(
                             nodeType.tag,
                             componentTypes,
                             textProps.get(davxml.displayname, None),
-                            principal,
-                            textProps.get(csxml.getctag, None),
+                            href,
+                            textProps.get(changeTag, None),
                             ))
                     break
         return calendars
 
 
-    def _principalPropfindInitial(self, user):
+    def _updateCalendar(self, calendar, newToken):
         """
-        Issue a PROPFIND on the /principals/users/<uid> URL to retrieve
-        the /principals/__uids__/<guid> principal URL
+        Update the local cached data for a calendar in an appropriate manner.
         """
-        principalURL = '/principals/users/' + user + '/'
-        d = self._request(
-            MULTI_STATUS,
-            'PROPFIND',
-            self.root + principalURL[1:].encode('utf-8'),
-            Headers({
-                    'content-type': ['text/xml'],
-                    'depth': ['0']}),
-            StringProducer(self._STARTUP_PRINCIPAL_PROPFIND_INITIAL))
-        d.addCallback(readBody)
-        d.addCallback(self._parseMultiStatus)
-        def get(result):
-            return result[principalURL]
-        d.addCallback(get)
-        return d
+        if self.supportSync:
+            return self._updateCalendar_SYNC(calendar, newToken)
+        else:
+            return self._updateCalendar_PROPFIND(calendar, newToken)
 
-
-    def _principalPropfind(self):
+    @inlineCallbacks
+    def _updateCalendar_PROPFIND(self, calendar, newToken):
         """
-        Issue a PROPFIND on the likely principal URL for the given
-        user and return a L{Principal} instance constructed from the
-        response.
+        Sync a collection by doing a full PROPFIND Depth:1 on it and then sync
+        the results with local cached data.
         """
-        d = self._request(
-            MULTI_STATUS,
-            'PROPFIND',
-            self.root + self.principalURL[1:].encode('utf-8'),
-            Headers({
-                    'content-type': ['text/xml'],
-                    'depth': ['0']}),
-            StringProducer(self._STARTUP_PRINCIPAL_PROPFIND))
-        d.addCallback(readBody)
-        d.addCallback(self._parseMultiStatus)
-        def get(result):
-            return result[self.principalURL]
-        d.addCallback(get)
-        return d
 
+        # Grab old hrefs prior to the PROPFIND so we sync with the old state. We need this because
+        # the sim can fire a PUT between the PROPFIND and when process the removals.
+        old_hrefs = set([calendar.url + child for child in calendar.events.keys()])
 
-    def _principalsReport(self, principalCollectionSet):
-        if principalCollectionSet.startswith('/'):
-            principalCollectionSet = principalCollectionSet[1:]
-        d = self._request(
-            OK,
-            'REPORT',
-            self.root + principalCollectionSet,
-            Headers({
-                    'content-type': ['text/xml'],
-                    'depth': ['0']}),
-            StringProducer(self._STARTUP_PRINCIPALS_REPORT))
-        d.addCallback(readBody)
-        return d
+        result = yield self._propfind(
+            calendar.url,
+            self._POLL_CALENDAR_PROPFIND,
+            depth='1',
+            method_label="PROPFIND{calendar}"
+        )
 
+        yield self._updateApplyChanges(calendar, result, old_hrefs)
 
-    def _calendarHomePropfind(self, calendarHomeSet):
-        if calendarHomeSet.startswith('/'):
-            calendarHomeSet = calendarHomeSet[1:]
-        if not calendarHomeSet.endswith('/'):
-            calendarHomeSet = calendarHomeSet + '/'
-        d = self._request(
-            MULTI_STATUS,
-            'PROPFIND',
-            self.root + calendarHomeSet,
-            Headers({
-                    'content-type': ['text/xml'],
-                    'depth': ['1']}),
-            StringProducer(self._STARTUP_CALENDARHOME_PROPFIND))
-        d.addCallback(readBody)
-        d.addCallback(self._extractCalendars, '/' + calendarHomeSet)
-        return d
+        # Now update calendar to the new token
+        self._calendars[calendar.url].changeToken = newToken
 
 
     @inlineCallbacks
-    def _updateCalendar(self, calendar):
-        url = calendar.url
-        if url.startswith('/'):
-            url = url[1:]
+    def _updateCalendar_SYNC(self, calendar, newToken):
+        """
+        Execute a sync REPORT against a calendar and apply changes to the local cache.
+        The new token from the changed collection is passed in and must be applied to
+        the existing calendar once sync is done.
+        """
 
-        # First do a PROPFIND on the calendar to learn about events it
-        # might have.
-        response = yield self._request(
-            MULTI_STATUS,
-            'PROPFIND',
-            self.root + url,
-            Headers({'content-type': ['text/xml'], 'depth': ['1']}),
-            StringProducer(self._CALENDAR_PROPFIND))
+        # Get changes from sync REPORT (including the other nodes at the top-level
+        # which will have the new sync token.
+        result, others = yield self._report(
+            calendar.url,
+            self._POLL_CALENDAR_SYNC_REPORT % {'sync-token': calendar.changeToken},
+            depth='1',
+            otherTokens = True,
+            method_label="REPORT{sync}",
+        )
 
-        body = yield readBody(response)
-
-        result = self._parseMultiStatus(body)
         changed = []
         for responseHref in result:
             if responseHref == calendar.url:
@@ -445,23 +626,78 @@
                 # XXX Ignore things with no etag?  Seems to be dropbox.
                 continue
 
+            # Differentiate a remove vs new/update result
+            if result[responseHref].getStatus() / 100 == 2:
+                if responseHref not in self._events:
+                    self._setEvent(responseHref, Event(responseHref, None))
+                    
+                event = self._events[responseHref]
+                if event.etag != etag:
+                    changed.append(responseHref)
+            elif result[responseHref].getStatus() == 404:
+                self._removeEvent(responseHref)
+
+        yield self._updateChangedEvents(calendar, changed)
+
+        # Now update calendar to the new token taken from the report
+        for node in others:
+            if node.tag == davxml.sync_token:
+                newToken = node.text
+                break
+        self._calendars[calendar.url].changeToken = newToken
+
+
+    @inlineCallbacks
+    def _updateApplyChanges(self, calendar, multistatus, old_hrefs):
+        """
+        Given a multistatus for an entire collection, sync the reported items
+        against the cached items.
+        """
+        
+        # Detect changes and new items
+        all_hrefs = []
+        changed_hrefs = []
+        for responseHref in multistatus:
+            if responseHref == calendar.url:
+                continue
+            all_hrefs.append(responseHref)
+            try:
+                etag = multistatus[responseHref].getTextProperties()[davxml.getetag]
+            except KeyError:
+                # XXX Ignore things with no etag?  Seems to be dropbox.
+                continue
+
             if responseHref not in self._events:
                 self._setEvent(responseHref, Event(responseHref, None))
                 
             event = self._events[responseHref]
             if event.etag != etag:
-                changed.append(responseHref)
-            
+                changed_hrefs.append(responseHref)
+        
+        # Retrieve changes
+        yield self._updateChangedEvents(calendar, changed_hrefs)
+    
+        # Detect removed items and purge them
+        remove_hrefs = old_hrefs - set(all_hrefs)
+        for href in remove_hrefs:
+            self._removeEvent(href)
+
+        
+    @inlineCallbacks
+    def _updateChangedEvents(self, calendar, changed):
+        """
+        Given a set of changed hrefs, batch multiget them all to update the
+        local cache.
+        """
+
         while changed:
             batchedHrefs = changed[:self.MULTIGET_BATCH_SIZE]
             changed = changed[self.MULTIGET_BATCH_SIZE:]
     
-            response = yield self._eventReport(url, batchedHrefs)
-            body = yield readBody(response)
-            multistatus = self._parseMultiStatus(body)
+            multistatus = yield self._eventReport(calendar.url, batchedHrefs)
             for responseHref in batchedHrefs:
                 res = multistatus[responseHref]
-                if res.getStatus() is None or " 404 " not in res.getStatus():
+                if res.getStatus() == 200:
                     text = res.getTextProperties()
                     etag = text[davxml.getetag]
                     try:
@@ -484,116 +720,121 @@
     def _eventReport(self, calendar, events):
         # Next do a REPORT on events that might have information
         # we don't know about.
-        hrefs = "".join([self._CALENDAR_REPORT_HREF % {'href': event} for event in events])
-        return self._request(
-            MULTI_STATUS,
-            'REPORT',
-            self.root + calendar,
-            Headers({'content-type': ['text/xml']}),
-            StringProducer(self._CALENDAR_REPORT % {'hrefs': hrefs}))
+        hrefs = "".join([self._POLL_CALENDAR_MULTIGET_REPORT_HREF % {'href': event} for event in events])
+        return self._report(
+            calendar,
+            self._POLL_CALENDAR_MULTIGET_REPORT % {'hrefs': hrefs},
+            depth=None,
+            method_label="REPORT{multiget}",
+        )
 
 
-    def _checkCalendarsForEvents(self, calendarHomeSet):
+    @inlineCallbacks
+    def _checkCalendarsForEvents(self, calendarHomeSet, firstTime=False):
+        """
+        The actions a client does when polling for changes, or in response to a
+        push notification of a change. There are some actions done on the first poll
+        we should emulate.
+        """
+
+        try:
+            result = yield self._newOperation("poll", self._poll(calendarHomeSet, firstTime))
+        finally:
+            self._checking.remove(calendarHomeSet)
+        returnValue(result)
+
+    @inlineCallbacks
+    def _poll(self, calendarHomeSet, firstTime):
         if calendarHomeSet in self._checking:
-            return
+            returnValue(None)
         self._checking.add(calendarHomeSet)
-        d = self._calendarHomePropfind(calendarHomeSet)
-        @inlineCallbacks
-        def cbCalendars(calendars):
-            for cal in calendars:
-                if cal.url not in self._calendars:
-                    # Calendar seen for the first time - reload it
-                    self._calendars[cal.url] = cal
-                    yield self._updateCalendar(cal)
-                elif self._calendars[cal.url].ctag != cal.ctag:
-                    # Calendar changed - update to new ctag and reload
-                    self._calendars[cal.url].ctag = cal.ctag
-                    yield self._updateCalendar(cal)
-        d.addCallback(cbCalendars)
-        d = self._newOperation("poll", d)
-        def ebCalendars(reason):
-            reason.trap(IncorrectResponseCode)
-        d.addErrback(ebCalendars)
-        def cleanupChecking(passthrough):
-            self._checking.remove(calendarHomeSet)
-            return passthrough
-        d.addBoth(cleanupChecking)
-        return d
 
+        calendars, results = yield self._calendarHomePropfind(calendarHomeSet)
+        
+        # First time operations
+        if firstTime:
+            yield self._pollFirstTime1(results[calendarHomeSet], calendars)
 
-    def _notificationPropfind(self, notificationURL):
-        if notificationURL.startswith('/'):
-            notificationURL = notificationURL[1:]
-        d = self._request(
-            MULTI_STATUS,
-            'PROPFIND',
-            self.root + notificationURL,
-            Headers({
-                    'content-type': ['text/xml'],
-                    'depth': ['1']}),
-            StringProducer(self._STARTUP_NOTIFICATION_PROPFIND))
-        d.addCallback(readBody)
-        d.addCallback(self._extractCalendars)
-        return d
+        # Normal poll
+        for cal in calendars:
+            newToken = cal.changeToken
+            if cal.url not in self._calendars:
+                # Calendar seen for the first time - reload it
+                self._calendars[cal.url] = cal
+                cal.changeToken = ""
+                yield self._updateCalendar(self._calendars[cal.url], newToken)
+            elif self._calendars[cal.url].changeToken != newToken:
+                # Calendar changed - reload it
+                yield self._updateCalendar(self._calendars[cal.url], newToken)
 
-    
-    def _principalReport(self, principalURL):
-        if principalURL.startswith('/'):
-            principalURL = principalURL[1:]
-        d = self._request(
-            OK,
-            'REPORT',
-            self.root + principalURL,
-            Headers({
-                    'content-type': ['text/xml'],
-                    'depth': ['0']}),
-            StringProducer(self._STARTUP_PRINCIPAL_REPORT))
-        d.addCallback(readBody)
-        return d
+        # When there is no sync REPORT, clients have to do a full PROPFIND
+        # on the notification collection because there is no ctag
+        if self.notificationURL is not None and not self.supportSync:
+            yield self._notificationPropfind(self.notificationURL)
+            yield self._notificationChangesPropfind(self.notificationURL)
 
+        # One time delegate expansion
+        if firstTime:
+            yield self._pollFirstTime2()
 
     @inlineCallbacks
-    def startup(self):
+    def _pollFirstTime1(self, homeNode, calendars):
+        # Detect sync report if needed
+        if self.supportSync:
+            nodes = homeNode.getNodeProperties()
+            syncnodes = nodes[davxml.supported_report_set].findall(
+                str(davxml.supported_report) + "/" +
+                str(davxml.report) + "/" +
+                str(davxml.sync_collection)
+            )
+            self.supportSync = len(syncnodes) != 0
 
-        # PROPFIND /principals/users/<uid> to retrieve /principals/__uids__/<guid>
-        response = yield self._principalPropfindInitial(self.record.uid)
-        hrefs = response.getHrefProperties()
-        self.principalURL = hrefs[davxml.principal_URL].toString()
+        # Patch calendar properties
+        for cal in calendars:
+            if cal.name != "inbox":
+                yield self._proppatch(cal.url, self._STARTUP_PROPPATCH_CALENDAR_COLOR)
+                yield self._proppatch(cal.url, self._STARTUP_PROPPATCH_CALENDAR_ORDER)
+                yield self._proppatch(cal.url, self._STARTUP_PROPPATCH_CALENDAR_TIMEZONE)
 
-        # Using the actual principal URL, retrieve principal information
-        principal = yield self._principalPropfind()
 
-        hrefs = principal.getHrefProperties()
+    def _pollFirstTime2(self):
+        return self._principalExpand(self.principalURL)
 
-        # Remember our outbox
-        self.outbox = hrefs[caldavxml.schedule_outbox_URL].toString()
 
-        # Remember our own email-like principal address
-        for principalURL in hrefs[caldavxml.calendar_user_address_set]:
-            if principalURL.toString().startswith(u"mailto:"):
-                self.email = principalURL.toString()
-            elif principalURL.toString().startswith(u"urn:"):
-                self.uuid = principalURL.toString()
-        if self.email is None:
-            raise ValueError("Cannot operate without a mail-style principal URL")
+    @inlineCallbacks
+    def _notificationPropfind(self, notificationURL):
+        result = yield self._propfind(
+            notificationURL,
+            self._POLL_NOTIFICATION_PROPFIND,
+        )
+        returnValue(result)
 
-        # Do another kind of thing I guess
-        principalCollection = hrefs[davxml.principal_collection_set].toString()
-        (yield self._principalsReport(principalCollection))
+    
+    @inlineCallbacks
+    def _notificationChangesPropfind(self, notificationURL):
+        result = yield self._propfind(
+            notificationURL,
+            self._POLL_NOTIFICATION_PROPFIND_D1,
+            depth='1',
+        )
+        returnValue(result)
 
-        # Whatever
+    
+    @inlineCallbacks
+    def _principalExpand(self, principalURL):
+        result = yield self._report(
+            principalURL,
+            self._STARTUP_PRINCIPAL_EXPAND,
+            depth=None,
+            method_label="REPORT{expand}",
+        )
+        returnValue(result)
 
-        # Learn stuff I guess
-        # notificationURL = hrefs[csxml.notification_URL].toString()
-        # (yield self._notificationPropfind(notificationURL))
 
-        # More too
-        # principalURL = hrefs[davxml.principal_URL].toString()
-        # (yield self._principalReport(principalURL))
+    def startup(self):
+        raise NotImplementedError
 
-        returnValue(principal)
 
-
     def _calendarCheckLoop(self, calendarHome):
         """
         Periodically check the calendar home for changes to calendars.
@@ -603,19 +844,42 @@
         return pollCalendarHome.start(self.calendarHomePollInterval, now=False)
 
 
+    @inlineCallbacks
     def _newOperation(self, label, deferred):
         before = self.reactor.seconds()
-        msg(type="operation", phase="start", user=self.record.uid, label=label)
-        def finished(passthrough):
-            success = not isinstance(passthrough, Failure)
-            if not success:
-                passthrough.trap(IncorrectResponseCode)
-            after = self.reactor.seconds()
-            msg(type="operation", phase="end", duration=after - before,
-                user=self.record.uid, label=label, success=success)
-            return passthrough
-        deferred.addBoth(finished)
-        return deferred
+        msg(
+            type="operation",
+            phase="start",
+            user=self.record.uid, 
+            client_type=self._client_type,
+            client_id=self._client_id,
+            label=label,
+        )
+        
+        try:
+            result = yield deferred
+        except IncorrectResponseCode:
+            # Let this through
+            success = False
+            result = None
+        except:
+            # Anything else is fatal
+            raise
+        else:
+            success = True
+        
+        after = self.reactor.seconds()
+        msg(
+            type="operation",
+            phase="end",
+            duration=after - before,
+            user=self.record.uid,
+            client_type=self._client_type,
+            client_id=self._client_id,
+            label=label,
+            success=success,
+        )
+        returnValue(result)
 
 
     def _monitorPubSub(self, home, params):
@@ -648,9 +912,11 @@
             principal = yield self.startup()
             hrefs = principal.getHrefProperties()
             calendarHome = hrefs[caldavxml.calendar_home_set].toString()
-            yield self._checkCalendarsForEvents(calendarHome)
+            yield self._checkCalendarsForEvents(calendarHome, firstTime=True)
             returnValue(calendarHome)
-        calendarHome = yield self._newOperation("startup", startup())
+        calendarHome = yield self._newOperation("startup: %s" % (self._client_type,), startup())
+        
+        self.started = True
 
         # Start monitoring PubSub notifications, if possible.
         # _checkCalendarsForEvents populates self.xmpp if it finds
@@ -689,70 +955,80 @@
         return organizer
 
 
+    @inlineCallbacks
     def addEventAttendee(self, href, attendee):
 
-        # Temporarily use some non-test names (some which will return
-        # many results, and others which will return fewer) because the
-        # test account names are all too similar
-        # name = attendee.parameterValue('CN').encode("utf-8")
-        # prefix = name[:4].lower()
-        prefix = random.choice(["chris", "cyru", "dre", "eric", "morg",
-            "well", "wilfr", "witz"])
-
-        email = attendee.parameterValue('EMAIL').encode("utf-8")
-
         event = self._events[href]
         vevent = event.vevent
 
-        # First try to discover some names to supply to the
-        # auto-completion
-        d = self._request(
-            MULTI_STATUS, 'REPORT', self.root + 'principals/',
-            Headers({'content-type': ['text/xml']}),
-            StringProducer(self._USER_LIST_PRINCIPAL_PROPERTY_SEARCH % {
-                    'displayname': prefix,
-                    'email': prefix,
-                    'firstname': prefix,
-                    'lastname': prefix,
-                    }))
-        d.addCallback(readBody)
-        def specific(ignored):
+        # Trigger auto-complete behavior
+        yield self._attendeeAutoComplete(vevent, attendee)
+
+        # If the event has no attendees, add ourselves as an attendee.
+        attendees = list(vevent.mainComponent().properties('ATTENDEE'))
+        if len(attendees) == 0:
+            # First add ourselves as a participant and as the
+            # organizer.  In the future for this event we should
+            # already have those roles.
+            vevent.mainComponent().addProperty(self._makeSelfOrganizer())
+            vevent.mainComponent().addProperty(self._makeSelfAttendee())
+        attendees.append(attendee)
+        vevent.mainComponent().addProperty(attendee)
+
+        # At last, upload the new event definition
+        response = yield self._request(
+            (NO_CONTENT, PRECONDITION_FAILED,),
+            'PUT',
+            self.root + href[1:].encode('utf-8'),
+            Headers({
+                    'content-type': ['text/calendar'],
+                    'if-match': [event.etag]}),
+            StringProducer(vevent.getTextWithTimezones(includeTimezones=True)),
+            method_label="PUT{organizer}"
+        )
+
+        # Finally, re-retrieve the event to update the etag
+        yield self._updateEvent(response, href)
+
+
+    @inlineCallbacks
+    def _attendeeAutoComplete(self, vevent, attendee):
+
+        if self._ATTENDEE_LOOKUPS:
+            # Temporarily use some non-test names (some which will return
+            # many results, and others which will return fewer) because the
+            # test account names are all too similar
+            # name = attendee.parameterValue('CN').encode("utf-8")
+            # prefix = name[:4].lower()
+            prefix = random.choice(["chris", "cyru", "dre", "eric", "morg",
+                "well", "wilfr", "witz"])
+    
+            email = attendee.parameterValue('EMAIL').encode("utf-8")
+    
+            # First try to discover some names to supply to the
+            # auto-completion
+            response = yield self._request(
+                MULTI_STATUS, 'REPORT', self.root + 'principals/',
+                Headers({'content-type': ['text/xml']}),
+                StringProducer(self._USER_LIST_PRINCIPAL_PROPERTY_SEARCH % {
+                        'displayname': prefix,
+                        'email': prefix,
+                        'firstname': prefix,
+                        'lastname': prefix,
+                        }),
+                method_label="REPORT{psearch}",
+            )
+            yield readBody(response)
+    
             # Now learn about the attendee's availability
-            return self.requestAvailability(
+            yield self.requestAvailability(
                 vevent.mainComponent().getStartDateUTC(),
                 vevent.mainComponent().getEndDateUTC(),
                 [self.email, u'mailto:' + email],
                 [vevent.resourceUID()])
-            return d
-        d.addCallback(specific)
-        def availability(ignored):
-            # If the event has no attendees, add ourselves as an attendee.
-            attendees = list(vevent.mainComponent().properties('ATTENDEE'))
-            if len(attendees) == 0:
-                # First add ourselves as a participant and as the
-                # organizer.  In the future for this event we should
-                # already have those roles.
-                vevent.mainComponent().addProperty(self._makeSelfOrganizer())
-                vevent.mainComponent().addProperty(self._makeSelfAttendee())
-            attendees.append(attendee)
-            vevent.mainComponent().addProperty(attendee)
 
-            # At last, upload the new event definition
-            d = self._request(
-                (NO_CONTENT, PRECONDITION_FAILED,),
-                'PUT',
-                self.root + href[1:].encode('utf-8'),
-                Headers({
-                        'content-type': ['text/calendar'],
-                        'if-match': [event.etag]}),
-                StringProducer(vevent.getTextWithTimezones(includeTimezones=True)))
-            return d
-        d.addCallback(availability)
-        # Finally, re-retrieve the event to update the etag
-        d.addCallback(self._updateEvent, href)
-        return d
 
-
+    @inlineCallbacks
     def changeEventAttendee(self, href, oldAttendee, newAttendee):
         event = self._events[href]
         vevent = event.vevent
@@ -768,56 +1044,62 @@
             headers.addRawHeader('if-schedule-tag-match', event.scheduleTag)
             okCodes = (NO_CONTENT, PRECONDITION_FAILED,)
 
-        d = self._request(
+        response = yield self._request(
             okCodes,
             'PUT',
             self.root + href[1:].encode('utf-8'),
-            headers, StringProducer(vevent.getTextWithTimezones(includeTimezones=True)))
-        d.addCallback(self._updateEvent, href)
-        return d
+            headers, StringProducer(vevent.getTextWithTimezones(includeTimezones=True)),
+            method_label="PUT{attendee}",
+        )
+        self._updateEvent(response, href)
 
 
+    @inlineCallbacks
     def deleteEvent(self, href):
         """
         Issue a DELETE for the given URL and remove local state
         associated with that event.
         """
-        d = self._request(
+        
+        self._removeEvent(href)
+
+        response = yield self._request(
             NO_CONTENT, 'DELETE', self.root + href[1:].encode('utf-8'))
+        returnValue(response)
 
-        calendar, uid = href.rsplit('/', 1)
-        del self._events[href]
-        del self._calendars[calendar + u'/'].events[uid]
 
-        return d
-
-
-    def addEvent(self, href, vcalendar):
+    @inlineCallbacks
+    def addEvent(self, href, vcalendar, invite=False):
         headers = Headers({
                 'content-type': ['text/calendar'],
                 })
-        d = self._request(
-            CREATED, 'PUT', self.root + href[1:].encode('utf-8'),
-            headers, StringProducer(vcalendar.getTextWithTimezones(includeTimezones=True)))
-        d.addCallback(self._localUpdateEvent, href, vcalendar)
-        return d
+        response = yield self._request(
+            CREATED,
+            'PUT',
+            self.root + href[1:].encode('utf-8'),
+            headers,
+            StringProducer(vcalendar.getTextWithTimezones(includeTimezones=True)),
+            method_label="PUT{organizer}" if invite else None,
+        )
+        self._localUpdateEvent(response, href, vcalendar)
 
+
     @inlineCallbacks
-    def addInvite(self, href, vcalendar):
+    def addInvite(self, href, vevent):
         """
         Add an event that is an invite - i.e., has attendees. We will do attendee lookups and freebusy
         checks on each attendee to simulate what happens when an organizer creates a new invite.
         """
         
         # Do lookup and free busy of each attendee (not self)
-        attendees = list(vcalendar.mainComponent().properties('ATTENDEE'))
+        attendees = list(vevent.mainComponent().properties('ATTENDEE'))
         for attendee in attendees:
             if attendee.value() == self.uuid:
                 continue
-            yield self.checkAttendee(vcalendar, attendee)
+            yield self._attendeeAutoComplete(vevent, attendee)
         
         # Now do a normal PUT
-        yield self.addEvent(href, vcalendar)
+        yield self.addEvent(href, vevent, invite=True)
 
 
     def _localUpdateEvent(self, response, href, vcalendar):
@@ -834,58 +1116,17 @@
         return self._updateEvent(None, href)
 
 
+    @inlineCallbacks
     def _updateEvent(self, ignored, href):
-        d = self._request(OK, 'GET', self.root + href[1:].encode('utf-8'))
-        def getETag(response):
-            headers = response.headers
-            etag = headers.getRawHeaders('etag')[0]
-            scheduleTag = headers.getRawHeaders('schedule-tag', [None])[0]
-            return readBody(response).addCallback(
-                lambda body: (etag, scheduleTag, body))
-        d.addCallback(getETag)
-        def record((etag, scheduleTag, body)):
-            self.eventChanged(href, etag, scheduleTag, body)
-        d.addCallback(record)
-        return d
+        response = yield self._request(OK, 'GET', self.root + href[1:].encode('utf-8'))
+        headers = response.headers
+        etag = headers.getRawHeaders('etag')[0]
+        scheduleTag = headers.getRawHeaders('schedule-tag', [None])[0]
+        body = yield readBody(response)
+        self.eventChanged(href, etag, scheduleTag, body)
 
 
     @inlineCallbacks
-    def checkAttendee(self, vcalendar, attendee):
-        """
-        This is what the client does when a user does attendee auto-complete whilst creating an
-        event invite. We will run this once for each attendee in a new invite.
-        """
-
-        # Temporarily use some non-test names (some which will return
-        # many results, and others which will return fewer) because the
-        # test account names are all too similar
-        # name = attendee.parameterValue('CN').encode("utf-8")
-        # prefix = name[:4].lower()
-        prefix = random.choice(["chris", "cyru", "dre", "eric", "morg",
-            "well", "wilfr", "witz"])
-
-        email = attendee.parameterValue('EMAIL').encode("utf-8")
-
-        # First try to discover some names to supply to the
-        # auto-completion - we will ignore the response
-        yield self._request(
-            MULTI_STATUS, 'REPORT', self.root + 'principals/',
-            Headers({'content-type': ['text/xml']}),
-            StringProducer(self._USER_LIST_PRINCIPAL_PROPERTY_SEARCH % {
-                    'displayname': prefix,
-                    'email': prefix,
-                    'firstname': prefix,
-                    'lastname': prefix,
-                    }))
-
-        # Now learn about the attendee's availability
-        yield self.requestAvailability(
-            vcalendar.mainComponent().getStartDateUTC(),
-            vcalendar.mainComponent().getEndDateUTC(),
-            [u'mailto:' + email],
-            [vcalendar.resourceUID()])
-
-
     def requestAvailability(self, start, end, users, mask=set()):
         """
         Issue a VFREEBUSY request for I{roughly} the given date range for the
@@ -931,7 +1172,7 @@
         end = end.getText()
         now = PyCalendarDateTime.getNowUTC().getText()
 
-        d = self._request(
+        response = yield self._request(
             OK, 'POST', outbox,
             Headers({
                     'content-type': ['text/calendar'],
@@ -945,12 +1186,404 @@
                     'event-mask': maskStr,
                     'start': start,
                     'end': end,
-                    'now': now}))
-        d.addCallback(readBody)
-        return d
+                    'now': now}),
+            method_label="POST{fb}",
+        )
+        body = yield readBody(response)
+        returnValue(body)
 
 
 
+class OS_X_10_6(BaseAppleClient):
+    """
+    Implementation of the OS X 10.6 iCal network behavior.
+
+    Anything OS X 10.6 iCal does on its own, or any particular
+    network behaviors it takes in response to a user action, belong on
+    this class.
+
+    Usage-profile based behaviors ("the user modifies an event every
+    3.2 minutes") belong elsewhere.
+    """
+
+    _client_type = "OS X 10.6"
+
+    USER_AGENT = "DAVKit/4.0.3 (732); CalendarStore/4.0.3 (991); iCal/4.0.3 (1388); Mac OS X/10.6.4 (10F569)"
+
+    # 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 = 15 * 60
+    
+    # The maximum number of resources to retrieve in a single multiget
+    MULTIGET_BATCH_SIZE = 200
+
+    # Override and turn on if client supports Sync REPORT
+    _SYNC_REPORT = False
+
+    # Override and turn on if client syncs using time-range queries
+    _SYNC_TIMERANGE = False
+
+    # Override and turn off if client does not support attendee lookups
+    _ATTENDEE_LOOKUPS = True
+
+    # Request body data
+    _LOAD_PATH = "OS_X_10_6"
+
+    _STARTUP_WELL_KNOWN = loadRequestBody(_LOAD_PATH, 'startup_well_known')
+    _STARTUP_PRINCIPAL_PROPFIND_INITIAL = loadRequestBody(_LOAD_PATH, 'startup_principal_propfind_initial')
+    _STARTUP_PRINCIPAL_PROPFIND = loadRequestBody(_LOAD_PATH, 'startup_principal_propfind')
+    _STARTUP_PRINCIPALS_REPORT = loadRequestBody(_LOAD_PATH, 'startup_principals_report')
+    _STARTUP_PRINCIPAL_EXPAND = loadRequestBody(_LOAD_PATH, 'startup_principal_expand')
+    _STARTUP_PROPPATCH_CALENDAR_COLOR = loadRequestBody(_LOAD_PATH, 'startup_calendar_color_proppatch')
+    _STARTUP_PROPPATCH_CALENDAR_ORDER = loadRequestBody(_LOAD_PATH, 'startup_calendar_order_proppatch')
+    _STARTUP_PROPPATCH_CALENDAR_TIMEZONE = loadRequestBody(_LOAD_PATH, 'startup_calendar_timezone_proppatch')
+
+    _POLL_CALENDARHOME_PROPFIND = loadRequestBody(_LOAD_PATH, 'poll_calendarhome_propfind')
+    _POLL_CALENDAR_PROPFIND = loadRequestBody(_LOAD_PATH, 'poll_calendar_propfind')
+    _POLL_CALENDAR_PROPFIND_D1 = loadRequestBody(_LOAD_PATH, 'poll_calendar_propfind_d1')
+    _POLL_CALENDAR_MULTIGET_REPORT = loadRequestBody(_LOAD_PATH, 'poll_calendar_multiget')
+    _POLL_CALENDAR_MULTIGET_REPORT_HREF = loadRequestBody(_LOAD_PATH, 'poll_calendar_multiget_hrefs')
+    _POLL_CALENDAR_SYNC_REPORT = None
+    _POLL_NOTIFICATION_PROPFIND = loadRequestBody(_LOAD_PATH, 'poll_calendar_propfind')
+    _POLL_NOTIFICATION_PROPFIND_D1 = loadRequestBody(_LOAD_PATH, 'poll_notification_propfind_d1')
+
+    _USER_LIST_PRINCIPAL_PROPERTY_SEARCH = loadRequestBody(_LOAD_PATH, 'user_list_principal_property_search')
+    _POST_AVAILABILITY = loadRequestBody(_LOAD_PATH, 'post_availability')
+
+    @inlineCallbacks
+    def startup(self):
+
+        # PROPFIND /principals/users/<uid> to retrieve /principals/__uids__/<guid>
+        response = yield self._principalPropfindInitial(self.record.uid)
+        hrefs = response.getHrefProperties()
+        self.principalURL = hrefs[davxml.principal_URL].toString()
+
+        # Using the actual principal URL, retrieve principal information
+        principal = yield self._principalPropfind()
+
+        hrefs = principal.getHrefProperties()
+
+        # Remember our outbox and notifications
+        self.outbox = hrefs[caldavxml.schedule_outbox_URL].toString()
+        try:
+            self.notificationURL = hrefs[csxml.notification_URL].toString()
+        except KeyError:
+            self.notificationURL = None
+
+        # Remember our own email-like principal address
+        for principalURL in hrefs[caldavxml.calendar_user_address_set]:
+            if principalURL.toString().startswith(u"mailto:"):
+                self.email = principalURL.toString()
+            elif principalURL.toString().startswith(u"urn:"):
+                self.uuid = principalURL.toString()
+        if self.email is None:
+            raise ValueError("Cannot operate without a mail-style principal URL")
+
+        # Do another kind of thing I guess
+        principalCollection = hrefs[davxml.principal_collection_set].toString()
+        (yield self._principalSearchPropertySetReport(principalCollection))
+
+        returnValue(principal)
+
+
+class OS_X_10_7(BaseAppleClient):
+    """
+    Implementation of the OS X 10.7 iCal network behavior.
+    """
+
+    _client_type = "OS X 10.7"
+
+    USER_AGENT = "CalendarStore/5.0.2 (1166); iCal/5.0.2 (1571); Mac OS X/10.7.3 (11D50)"
+
+    # 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 = 15 * 60
+    
+    # The maximum number of resources to retrieve in a single multiget
+    MULTIGET_BATCH_SIZE = 200
+
+    # Override and turn on if client supports Sync REPORT
+    _SYNC_REPORT = True
+
+    # Override and turn on if client syncs using time-range queries
+    _SYNC_TIMERANGE = False
+
+    # Override and turn off if client does not support attendee lookups
+    _ATTENDEE_LOOKUPS = True
+
+    # Request body data
+    _LOAD_PATH = "OS_X_10_7"
+
+    _STARTUP_WELL_KNOWN = loadRequestBody(_LOAD_PATH, 'startup_well_known')
+    _STARTUP_PRINCIPAL_PROPFIND_INITIAL = loadRequestBody(_LOAD_PATH, 'startup_principal_propfind_initial')
+    _STARTUP_PRINCIPAL_PROPFIND = loadRequestBody(_LOAD_PATH, 'startup_principal_propfind')
+    _STARTUP_PRINCIPALS_REPORT = loadRequestBody(_LOAD_PATH, 'startup_principals_report')
+    _STARTUP_PRINCIPAL_EXPAND = loadRequestBody(_LOAD_PATH, 'startup_principal_expand')
+    _STARTUP_PROPPATCH_CALENDAR_COLOR = loadRequestBody(_LOAD_PATH, 'startup_calendar_color_proppatch')
+    _STARTUP_PROPPATCH_CALENDAR_ORDER = loadRequestBody(_LOAD_PATH, 'startup_calendar_order_proppatch')
+    _STARTUP_PROPPATCH_CALENDAR_TIMEZONE = loadRequestBody(_LOAD_PATH, 'startup_calendar_timezone_proppatch')
+
+    _POLL_CALENDARHOME_PROPFIND = loadRequestBody(_LOAD_PATH, 'poll_calendarhome_propfind')
+    _POLL_CALENDAR_PROPFIND = loadRequestBody(_LOAD_PATH, 'poll_calendar_propfind')
+    _POLL_CALENDAR_PROPFIND_D1 = loadRequestBody(_LOAD_PATH, 'poll_calendar_propfind_d1')
+    _POLL_CALENDAR_MULTIGET_REPORT = loadRequestBody(_LOAD_PATH, 'poll_calendar_multiget')
+    _POLL_CALENDAR_MULTIGET_REPORT_HREF = loadRequestBody(_LOAD_PATH, 'poll_calendar_multiget_hrefs')
+    _POLL_CALENDAR_SYNC_REPORT = loadRequestBody(_LOAD_PATH, 'poll_calendar_sync')
+    _POLL_NOTIFICATION_PROPFIND = loadRequestBody(_LOAD_PATH, 'poll_calendar_propfind')
+    _POLL_NOTIFICATION_PROPFIND_D1 = loadRequestBody(_LOAD_PATH, 'poll_notification_propfind_d1')
+
+    _USER_LIST_PRINCIPAL_PROPERTY_SEARCH = loadRequestBody(_LOAD_PATH, 'user_list_principal_property_search')
+    _POST_AVAILABILITY = loadRequestBody(_LOAD_PATH, 'post_availability')
+
+
+    def _addDefaultHeaders(self, headers):
+        """
+        Add the clients default set of headers to ones being used in a request.
+        Default is to add User-Agent, sub-classes should override to add other
+        client specific things, Accept etc.
+        """
+        
+        super(OS_X_10_7, self)._addDefaultHeaders(headers)
+        headers.setRawHeaders('Accept', ['*/*'])
+        headers.setRawHeaders('Accept-Language', ['en-us'])
+        headers.setRawHeaders('Accept-Encoding', ['gzip,deflate'])
+        headers.setRawHeaders('Connection', ['keep-alive'])
+
+
+    @inlineCallbacks
+    def startup(self):
+
+        # PROPFIND well-known - ignore
+        yield self._startupPropfindWellKnown()
+        
+        # PROPFIND / - ignore
+        yield self._startupPropfindRoot()
+        
+        # PROPFIND /principals/users/<uid> to retrieve /principals/__uids__/<guid>
+        response = yield self._principalPropfindInitial(self.record.uid)
+        hrefs = response.getHrefProperties()
+        self.principalURL = hrefs[davxml.principal_URL].toString()
+
+        # Using the actual principal URL, retrieve principal information
+        principal = yield self._principalPropfind()
+
+        hrefs = principal.getHrefProperties()
+
+        # Remember our outbox and notifications
+        self.outbox = hrefs[caldavxml.schedule_outbox_URL].toString()
+        try:
+            self.notificationURL = hrefs[csxml.notification_URL].toString()
+        except KeyError:
+            self.notificationURL = None
+
+        # Remember our own email-like principal address
+        for principalURL in hrefs[caldavxml.calendar_user_address_set]:
+            if principalURL.toString().startswith(u"mailto:"):
+                self.email = principalURL.toString()
+            elif principalURL.toString().startswith(u"urn:"):
+                self.uuid = principalURL.toString()
+        if self.email is None:
+            raise ValueError("Cannot operate without a mail-style principal URL")
+
+        # Do another kind of thing I guess
+        principalCollection = hrefs[davxml.principal_collection_set].toString()
+        yield self._principalSearchPropertySetReport(principalCollection)
+
+        returnValue(principal)
+
+
+
+class iOS_5(BaseAppleClient):
+    """
+    Implementation of the iOS 5 network behavior.
+    """
+
+    _client_type = "iOS 5"
+
+    USER_AGENT = "iOS/5.1 (9B179) dataaccessd/1.0"
+
+    # 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 = 15 * 60
+    
+    # The maximum number of resources to retrieve in a single multiget
+    MULTIGET_BATCH_SIZE = 50
+
+    # Override and turn on if client supports Sync REPORT
+    _SYNC_REPORT = False
+
+    # Override and turn on if client syncs using time-range queries
+    _SYNC_TIMERANGE = True
+
+    # Override and turn off if client does not support attendee lookups
+    _ATTENDEE_LOOKUPS = False
+
+    # Request body data
+    _LOAD_PATH = "iOS_5"
+
+    _STARTUP_WELL_KNOWN = loadRequestBody(_LOAD_PATH, 'startup_well_known')
+    _STARTUP_PRINCIPAL_PROPFIND_INITIAL = loadRequestBody(_LOAD_PATH, 'startup_principal_propfind_initial')
+    _STARTUP_PRINCIPAL_PROPFIND = loadRequestBody(_LOAD_PATH, 'startup_principal_propfind')
+    _STARTUP_PRINCIPALS_REPORT = loadRequestBody(_LOAD_PATH, 'startup_principals_report')
+    _STARTUP_PROPPATCH_CALENDAR_COLOR = loadRequestBody(_LOAD_PATH, 'startup_calendar_color_proppatch')
+    _STARTUP_PROPPATCH_CALENDAR_ORDER = loadRequestBody(_LOAD_PATH, 'startup_calendar_order_proppatch')
+
+    _POLL_CALENDARHOME_PROPFIND = loadRequestBody(_LOAD_PATH, 'poll_calendarhome_propfind')
+    _POLL_CALENDAR_PROPFIND = loadRequestBody(_LOAD_PATH, 'poll_calendar_propfind')
+    _POLL_CALENDAR_VEVENT_TR_QUERY = loadRequestBody(_LOAD_PATH, 'poll_calendar_vevent_tr_query')
+    _POLL_CALENDAR_VTODO_QUERY = loadRequestBody(_LOAD_PATH, 'poll_calendar_vtodo_query')
+    _POLL_CALENDAR_PROPFIND_D1 = loadRequestBody(_LOAD_PATH, 'poll_calendar_propfind_d1')
+    _POLL_CALENDAR_MULTIGET_REPORT = loadRequestBody(_LOAD_PATH, 'poll_calendar_multiget')
+    _POLL_CALENDAR_MULTIGET_REPORT_HREF = loadRequestBody(_LOAD_PATH, 'poll_calendar_multiget_hrefs')
+
+
+    def _addDefaultHeaders(self, headers):
+        """
+        Add the clients default set of headers to ones being used in a request.
+        Default is to add User-Agent, sub-classes should override to add other
+        client specific things, Accept etc.
+        """
+        
+        super(iOS_5, self)._addDefaultHeaders(headers)
+        headers.setRawHeaders('Accept', ['*/*'])
+        headers.setRawHeaders('Accept-Language', ['en-us'])
+        headers.setRawHeaders('Accept-Encoding', ['gzip,deflate'])
+        headers.setRawHeaders('Connection', ['keep-alive'])
+
+
+    @inlineCallbacks
+    def _principalPropfindInitial(self):
+        """
+        Issue a PROPFIND on the /principals/ URL to retrieve
+        the /principals/__uids__/<guid> principal URL
+        """
+        result = yield self._propfind(
+            '/principals/',
+            self._STARTUP_PRINCIPAL_PROPFIND_INITIAL,
+        )
+        returnValue(result['/principals/'])
+
+
+    @inlineCallbacks
+    def _pollFirstTime1(self, homeNode, calendars):
+        # Patch calendar properties
+        for cal in calendars:
+            if cal.name != "inbox":
+                yield self._proppatch(cal.url, self._STARTUP_PROPPATCH_CALENDAR_COLOR)
+                yield self._proppatch(cal.url, self._STARTUP_PROPPATCH_CALENDAR_ORDER)
+
+
+    def _pollFirstTime2(self):
+        # Nothing here
+        return succeed(None)
+
+
+    def _updateCalendar(self, calendar, newToken):
+        """
+        Update the local cached data for a calendar in an appropriate manner.
+        """
+        if calendar.name == "inbox":
+            # Inbox is done as a PROPFIND Depth:1
+            return self._updateCalendar_PROPFIND(calendar, newToken)
+        elif "VEVENT" in calendar.componentTypes:
+            # VEVENTs done as time-range VEVENT-only queries
+            return self._updateCalendar_VEVENT(calendar, newToken)
+        elif "VTODO" in calendar.componentTypes:
+            # VTODOs done as VTODO-only queries
+            return self._updateCalendar_VTODO(calendar, newToken)
+
+
+    @inlineCallbacks
+    def _updateCalendar_VEVENT(self, calendar, newToken):
+        """
+        Sync all locally cached VEVENTs using a VEVENT-only time-range query.
+        """
+
+        # Grab old hrefs prior to the PROPFIND so we sync with the old state. We need this because
+        # the sim can fire a PUT between the PROPFIND and when process the removals.
+        old_hrefs = set([calendar.url + child for child in calendar.events.keys()])
+
+        now = PyCalendarDateTime.getNowUTC()
+        now.setDateOnly(True)
+        now.offsetMonth(-1) # 1 month back default
+        result = yield self._report(
+            calendar.url,
+            self._POLL_CALENDAR_VEVENT_TR_QUERY % {"start-date":now.getText()},
+            depth='1',
+            method_label="REPORT{vevent}",
+        )
+
+        yield self._updateApplyChanges(calendar, result, old_hrefs)
+
+        # Now update calendar to the new token
+        self._calendars[calendar.url].changeToken = newToken
+
+
+    @inlineCallbacks
+    def _updateCalendar_VTODO(self, calendar, newToken):
+        """
+        Sync all locally cached VTODOs using a VTODO-only query.
+        """
+
+        # Grab old hrefs prior to the PROPFIND so we sync with the old state. We need this because
+        # the sim can fire a PUT between the PROPFIND and when process the removals.
+        old_hrefs = set([calendar.url + child for child in calendar.events.keys()])
+
+        result = yield self._report(
+            calendar.url,
+            self._POLL_CALENDAR_VTODO_QUERY,
+            depth='1',
+            method_label="REPORT{vtodo}",
+        )
+
+        yield self._updateApplyChanges(calendar, result, old_hrefs)
+
+        # Now update calendar to the new token
+        self._calendars[calendar.url].changeToken = newToken
+
+
+    @inlineCallbacks
+    def startup(self):
+
+        # PROPFIND well-known - ignore
+        yield self._startupPropfindWellKnown()
+        
+        # PROPFIND / - ignore
+        yield self._startupPropfindRoot()
+        
+        # PROPFIND /principals/ to retrieve /principals/__uids__/<guid>
+        response = yield self._principalPropfindInitial()
+        hrefs = response.getHrefProperties()
+        self.principalURL = hrefs[davxml.current_user_principal].toString()
+
+        # Using the actual principal URL, retrieve principal information
+        principal = yield self._principalPropfind()
+
+        hrefs = principal.getHrefProperties()
+
+        # Remember our outbox and ignore notifications
+        self.outbox = hrefs[caldavxml.schedule_outbox_URL].toString()
+        self.notificationURL = None
+
+        # Remember our own email-like principal address
+        for principalURL in hrefs[caldavxml.calendar_user_address_set]:
+            if principalURL.toString().startswith(u"mailto:"):
+                self.email = principalURL.toString()
+            elif principalURL.toString().startswith(u"urn:"):
+                self.uuid = principalURL.toString()
+        if self.email is None:
+            raise ValueError("Cannot operate without a mail-style principal URL")
+
+        # Do another kind of thing I guess
+        principalCollection = hrefs[davxml.principal_collection_set].toString()
+        yield self._principalSearchPropertySetReport(principalCollection)
+
+        returnValue(principal)
+
+
 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}"
@@ -995,13 +1628,13 @@
     addObserver(RequestLogger().observe)
 
     from sim import _DirectoryRecord
-    client = SnowLeopard(
+    client = OS_X_10_6(
         reactor, 'http://127.0.0.1:8008/', 
         _DirectoryRecord(
             u'user01', u'user01', u'User 01', u'user01 at example.org'),
         auth)
     d = client.run()
-    d.addErrback(err, "Snow Leopard client run() problem")
+    d.addErrback(err, "10.6 client run() problem")
     d.addCallback(lambda ignored: reactor.stop())
     reactor.run()
 

Modified: CalendarServer/trunk/contrib/performance/loadtest/logger.py
===================================================================
--- CalendarServer/trunk/contrib/performance/loadtest/logger.py	2012-04-10 00:41:41 UTC (rev 9003)
+++ CalendarServer/trunk/contrib/performance/loadtest/logger.py	2012-04-10 15:41:35 UTC (rev 9004)
@@ -1,5 +1,5 @@
 ##
-# Copyright (c) 2011 Apple Inc. All rights reserved.
+# Copyright (c) 2011-2012 Apple Inc. All rights reserved.
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
 # you may not use this file except in compliance with the License.
@@ -15,9 +15,10 @@
 #
 ##
 
-from contrib.performance.stats import mean, median
+from contrib.performance.stats import mean, median, stddev
 
 class SummarizingMixin(object):
+
     def printHeader(self, fields):
         """
         Print a header for the summarization data which will be reported.
@@ -32,23 +33,41 @@
         for (label, width) in fields:
             format.append('%%%ds' % (width,))
             labels.append(label)
-        print ' '.join(format) % tuple(labels)
+        header = ' '.join(format) % tuple(labels)
+        print header
+        print "-" * len(header)
 
 
     def _summarizeData(self, operation, data):
         failed = 0
-        threesec = 0
+        thresholds = [0] * len(self._thresholds)
         durations = []
         for (success, duration) in data:
             if not success:
                 failed += 1
-            if duration > 3:
-                threesec += 1
+            for ctr, item in enumerate(self._thresholds):
+                threshold, _ignore_fail_at = item
+                if duration > threshold:
+                    thresholds[ctr] += 1
             durations.append(duration)
 
-        return operation, len(data), failed, threesec, mean(durations), median(durations)
+        # Determine PASS/FAIL
+        failure = False
+        count = len(data)
+        
+        if failed * 100.0 / count > self._fail_cut_off:
+            failure = True
 
+        for ctr, item in enumerate(self._thresholds):
+            _ignore_threshold, fail_at = item
+            if thresholds[ctr] * 100.0 / count > fail_at:
+                failure = True
 
+        return (operation, count, failed,) + \
+                tuple(thresholds) + \
+                (mean(durations), median(durations), stddev(durations), "FAIL" if failure else "")
+
+
     def _printRow(self, formats, values):
         format = ' '.join(formats)
         print format % values

Modified: CalendarServer/trunk/contrib/performance/loadtest/population.py
===================================================================
--- CalendarServer/trunk/contrib/performance/loadtest/population.py	2012-04-10 00:41:41 UTC (rev 9003)
+++ CalendarServer/trunk/contrib/performance/loadtest/population.py	2012-04-10 15:41:35 UTC (rev 9004)
@@ -1,6 +1,6 @@
 # -*- test-case-name: contrib.performance.loadtest.test_population -*-
 ##
-# Copyright (c) 2010 Apple Inc. All rights reserved.
+# Copyright (c) 2010-2012 Apple Inc. All rights reserved.
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
 # you may not use this file except in compliance with the License.
@@ -26,6 +26,7 @@
 from tempfile import mkdtemp
 from itertools import izip
 
+from twisted.python.failure import Failure
 from twisted.python.filepath import FilePath
 from twisted.python.util import FancyEqMixin
 from twisted.python.log import msg, err
@@ -35,7 +36,7 @@
 from contrib.performance.stats import mean, median, stddev, mad
 from contrib.performance.loadtest.trafficlogger import loggedReactor
 from contrib.performance.loadtest.logger import SummarizingMixin
-from contrib.performance.loadtest.ical import SnowLeopard, RequestLogger
+from contrib.performance.loadtest.ical import OS_X_10_6, RequestLogger
 from contrib.performance.loadtest.profiles import Eventer, Inviter, Accepter
 
 
@@ -197,29 +198,32 @@
         self._stopped = True
 
 
-    def add(self, numClients):
+    def add(self, numClients, clientsPerUser):
         for _ignore_n in range(numClients):
             number = self._nextUserNumber()
-            clientType = self._pop.next()
-            if (number % self.workerCount) != self.workerIndex:
-                # If we're in a distributed work scenario and we are worker N,
-                # we have to skip all but every Nth request (since every node
-                # runs the same arrival policy).
-                continue
+            
+            for _ignore_peruser in range(clientsPerUser):
+                clientType = self._pop.next()
+                if (number % self.workerCount) != self.workerIndex:
+                    # If we're in a distributed work scenario and we are worker N,
+                    # we have to skip all but every Nth request (since every node
+                    # runs the same arrival policy).
+                    continue
+    
+                _ignore_user, auth = self._createUser(number)
+    
+                reactor = loggedReactor(self.reactor)
+                client = clientType.new(
+                    reactor, self.server, self.getUserRecord(number), auth)
+                d = client.run()
+                d.addErrback(self._clientFailure, reactor)
+    
+                for profileType in clientType.profileTypes:
+                    profile = profileType(reactor, self, client, number)
+                    if profile.enabled:
+                        d = profile.run()
+                        d.addErrback(self._profileFailure, profileType, reactor)
 
-            _ignore_user, auth = self._createUser(number)
-
-            reactor = loggedReactor(self.reactor)
-            client = clientType.new(
-                reactor, self.server, self.getUserRecord(number), auth)
-            d = client.run()
-            d.addErrback(self._clientFailure, reactor)
-
-            for profileType in clientType.profileTypes:
-                profile = profileType(reactor, self, client, number)
-                if profile.enabled:
-                    d = profile.run()
-                    d.addErrback(self._profileFailure, profileType, reactor)
         # XXX this status message is prone to be slightly inaccurate, but isn't
         # really used by much anyway.
         msg(type="status", clientCount=self._user - 1)
@@ -242,6 +246,9 @@
             where = self._dumpLogs(reactor, reason)
             err(reason, "Client stopped with error; recent traffic in %r" % (
                     where.path,))
+            if not isinstance(reason, Failure):
+                reason = Failure(reason)
+            msg(type="client-failure", reason="%s: %s" % (reason.type, reason.value,))
 
 
     def _profileFailure(self, reason, profileType, reactor):
@@ -253,17 +260,18 @@
 
 
 class SmoothRampUp(object):
-    def __init__(self, reactor, groups, groupSize, interval):
+    def __init__(self, reactor, groups, groupSize, interval, clientsPerUser):
         self.reactor = reactor
         self.groups = groups
         self.groupSize = groupSize
         self.interval = interval
+        self.clientsPerUser = clientsPerUser
 
 
     def run(self, simulator):
         for i in range(self.groups):
             self.reactor.callLater(
-                self.interval * i, simulator.add, self.groupSize)
+                self.interval * i, simulator.add, self.groupSize, self.clientsPerUser)
 
 
 
@@ -271,6 +279,8 @@
     def observe(self, event):
         if event.get('type') == 'response':
             self.eventReceived(event)
+        elif event.get('type') == 'client-failure':
+            self.clientFailure(event)
 
 
     def report(self):
@@ -297,7 +307,10 @@
             del self._times[:100]
 
 
+    def clientFailure(self, event):
+        pass
 
+
 class ReportStatistics(StatisticsBase, SummarizingMixin):
     """
 
@@ -306,30 +319,63 @@
         reported as the number of users in the simulation.
 
     """
+
+    # the response time thresholds to display together with failing % count threshold
+    _thresholds = (
+        (0.5, 100.0),  
+        (  1, 100.0),  
+        (  3,   5.0),  
+        (  5,   1.0),
+        ( 10,   0.5),
+    )
+    _fail_cut_off = 1.0     # % of total count at which failed requests will cause a failure 
+
     _fields = [
-        ('request', 10, '%10s'),
+        ('request', -20, '%-20s'),
         ('count', 8, '%8s'),
         ('failed', 8, '%8s'),
-        ('>3sec', 8, '%8s'),
+    ]
+    
+    for threshold, _ignore_fail_at in _thresholds:
+        _fields.append(('>%g sec' % (threshold,), 10, '%10s'))
+
+    _fields.extend([
         ('mean', 8, '%8.4f'),
         ('median', 8, '%8.4f'),
-        ]
+        ('stddev', 8, '%8.4f'),
+        ('STATUS', 8, '%8s'),
+    ])
 
     def __init__(self):
         self._perMethodTimes = {}
         self._users = set()
+        self._clients = set()
+        self._failed_clients = []
 
 
     def countUsers(self):
         return len(self._users)
 
 
+    def countClients(self):
+        return len(self._clients)
+
+
+    def countClientFailures(self):
+        return len(self._failed_clients)
+
+
     def eventReceived(self, event):
         dataset = self._perMethodTimes.setdefault(event['method'], [])
         dataset.append((event['success'], event['duration']))
         self._users.add(event['user'])
+        self._clients.add(event['client_id'])
 
 
+    def clientFailure(self, event):
+        self._failed_clients.append(event['reason'])
+
+
     def printMiscellaneous(self, items):
         for k, v in sorted(items.iteritems()):
             print k.title(), ':', v
@@ -337,7 +383,22 @@
 
     def report(self):
         print
-        self.printMiscellaneous({'users': self.countUsers()})
+        print "** REPORT **"
+        print
+        self.printMiscellaneous({
+            'users': self.countUsers(),
+            'clients': self.countClients(),
+        })
+        if self.countClientFailures() > 0:
+            self.printMiscellaneous({
+                'Failed clients': self.countClientFailures(),
+            })
+            for ctr, reason in enumerate(self._failed_clients, 1):
+                self.printMiscellaneous({
+                    'Failure #%d' % (ctr,): reason,
+                })
+            
+        print
         self.printHeader([
                 (label, width)
                 for (label, width, _ignore_fmt)
@@ -346,46 +407,40 @@
             [fmt for (label, width, fmt) in self._fields],
             sorted(self._perMethodTimes.items()))
 
-    _FAILED_REASON = "Greater than %(cutoff)0.f%% %(method)s failed"
-    _THREESEC_REASON = "Greater than %(cutoff)0.f%% %(method)s exceeded 3 second response time"
-    _FIVESEC_REASON = "Greater than %(cutoff)0.f%% %(method)s exceeded 5 second response time"
+    _FAILED_REASON = "Greater than %(cutoff)g%% %(method)s failed"
 
+    _REASON_1 = "Greater than %(cutoff)g%% %(method)s exceeded "
+    _REASON_2 = "%g second response time"
+
     def failures(self):
         # TODO
         reasons = []
 
-        # Upper limit on ratio of failed requests to total requests
-        failCutoff = 0.01
-
-        # Upper limit on ratio of >3sec requests to total requests
-        threeSecCutoff = 0.05
-
-        # Upper limit on ratio of >5sec requests to total requests
-        fiveSecCutoff = 0.01
-
         for (method, times) in self._perMethodTimes.iteritems():
             failures = 0
-            threeSec = 0
-            fiveSec = 0
+            overDurations = [0] * len(self._thresholds)
 
             for success, duration in times:
                 if not success:
                     failures += 1
-                if duration > 5:
-                    fiveSec += 1
-                elif duration > 3:
-                    threeSec += 1
+                for ctr, item in enumerate(self._thresholds):
+                    threshold, _ignore_fail_at = item
+                    if duration > threshold:
+                        overDurations[ctr] += 1
 
             checks = [
-                (failures, failCutoff, self._FAILED_REASON),
-                (threeSec, threeSecCutoff, self._THREESEC_REASON),
-                (fiveSec, fiveSecCutoff, self._FIVESEC_REASON),
+                (failures, self._fail_cut_off, self._FAILED_REASON),
                 ]
+            
+            for ctr, item in enumerate(self._thresholds):
+                threshold, fail_at = item
+                checks.append(
+                    (overDurations[ctr], fail_at, self._REASON_1 + self._REASON_2 % (threshold,))
+                )
 
             for count, cutoff, reason in checks:
-                if count / len(times) > cutoff:
-                    reasons.append(reason % dict(
-                            method=method, cutoff=cutoff * 100))
+                if count * 100.0 / len(times) > cutoff:
+                    reasons.append(reason % dict(method=method, cutoff=cutoff))
 
         return reasons
 
@@ -410,7 +465,7 @@
     populator = Populator(r)
     parameters = PopulationParameters()
     parameters.addClient(
-        1, ClientType(SnowLeopard, [Eventer, Inviter, Accepter]))
+        1, ClientType(OS_X_10_6, [Eventer, Inviter, Accepter]))
     simulator = CalendarClientSimulator(
         populator, parameters, reactor, '127.0.0.1', 8008)
 

Modified: CalendarServer/trunk/contrib/performance/loadtest/profiles.py
===================================================================
--- CalendarServer/trunk/contrib/performance/loadtest/profiles.py	2012-04-10 00:41:41 UTC (rev 9003)
+++ CalendarServer/trunk/contrib/performance/loadtest/profiles.py	2012-04-10 15:41:35 UTC (rev 9004)
@@ -90,8 +90,15 @@
         lag = context.get('lag', None)
 
         before = self._reactor.seconds()
-        msg(type="operation", phase="start",
-            user=self._client.record.uid, label=label, lag=lag)
+        msg(
+            type="operation",
+            phase="start",
+            user=self._client.record.uid,
+            client_type=self._client._client_type,
+            client_id=self._client._client_id,
+            label=label,
+            lag=lag,
+        )
 
         def finished(passthrough):
             success = not isinstance(passthrough, Failure)
@@ -99,8 +106,16 @@
                 passthrough.trap(IncorrectResponseCode)
                 passthrough = passthrough.value.response
             after = self._reactor.seconds()
-            msg(type="operation", phase="end", duration=after - before,
-                user=self._client.record.uid, label=label, success=success)
+            msg(
+                type="operation",
+                phase="end",
+                duration=after - before,
+                user=self._client.record.uid,
+                client_type=self._client._client_type,
+                client_id=self._client._client_id,
+                label=label,
+                success=success,
+            )
             return passthrough
         deferred.addBoth(finished)
         return deferred
@@ -122,7 +137,10 @@
 
     def iterate():
         d = function()
-        d.addCallbacks(repeat, result.errback)
+        if d is not None:
+            d.addCallbacks(repeat, result.errback)
+        else:
+            repeat(None)
 
     repeat(None)
     return result
@@ -198,6 +216,10 @@
             otherwise a L{Deferred} which fires when the attendee
             change has been made.
         """
+        
+        if not self._client.started:
+            return succeed(None)
+            
         # Find calendars which are eligible for invites
         calendars = self._calendarsOfType(caldavxml.calendar, "VEVENT")
 
@@ -341,6 +363,10 @@
             otherwise a L{Deferred} which fires when the attendee
             change has been made.
         """
+
+        if not self._client.started:
+            return succeed(None)
+            
         # Find calendars which are eligible for invites
         calendars = self._calendarsOfType(caldavxml.calendar, "VEVENT")
 
@@ -578,6 +604,9 @@
 
 
     def _addEvent(self):
+        if not self._client.started:
+            return succeed(None)
+            
         calendars = self._calendarsOfType(caldavxml.calendar, "VEVENT")
 
         while calendars:
@@ -639,6 +668,9 @@
 
 
     def _addTask(self):
+        if not self._client.started:
+            return succeed(None)
+            
         calendars = self._calendarsOfType(caldavxml.calendar, "VTODO")
 
         while calendars:
@@ -674,15 +706,33 @@
 
     lagFormat = u'{lag %5.2f ms}'
 
+    # the response time thresholds to display together with failing % count threshold
+    _thresholds = (
+        (0.5, 100.0),  
+        (  1, 100.0),  
+        (  3, 100.0),  
+        (  5, 100.0),
+        ( 10, 100.0),
+    )
+    _lag_cut_off = 1.0      # Maximum allowed median scheduling latency, seconds 
+    _fail_cut_off = 1.0     # % of total count at which failed requests will cause a failure 
+
     _fields = [
-        ('operation', 10, '%10s'),
+        ('operation', -20, '%-20s'),
         ('count', 8, '%8s'),
         ('failed', 8, '%8s'),
-        ('>3sec', 8, '%8s'),
+    ]
+    
+    for threshold, _ignore_fail_at in _thresholds:
+        _fields.append(('>%g sec' % (threshold,), 10, '%10s'))
+
+    _fields.extend([
         ('mean', 8, '%8.4f'),
         ('median', 8, '%8.4f'),
-        ('avglag (ms)', 8, '%8.4f'),
-        ]
+        ('stddev', 8, '%8.4f'),
+        ('avglag (ms)', 12, '%12.4f'),
+        ('STATUS', 8, '%8s'),
+    ])
 
     def __init__(self, outfile=None):
         self._perOperationTimes = {}
@@ -713,7 +763,8 @@
 
     def _summarizeData(self, operation, data):
         avglag = mean(self._perOperationLags.get(operation, [0.0])) * 1000.0
-        return SummarizingMixin._summarizeData(self, operation, data) + (avglag,)
+        data = SummarizingMixin._summarizeData(self, operation, data)
+        return data[:-1] + (avglag,) + data[-1:]
 
 
     def report(self):
@@ -732,21 +783,15 @@
     def failures(self):
         reasons = []
 
-        # Maximum allowed median scheduling latency, seconds
-        lagCutoff = 1.0
-
-        # Maximum allowed ratio of failed operations
-        failCutoff = 0.01
-
         for operation, lags in self._perOperationLags.iteritems():
-            if median(lags) > lagCutoff:
+            if median(lags) > self._lag_cut_off:
                 reasons.append(self._LATENCY_REASON % dict(
-                        operation=operation.upper(), cutoff=lagCutoff * 1000))
+                        operation=operation.upper(), cutoff=self._lag_cut_off * 1000))
 
         for operation, times in self._perOperationTimes.iteritems():
             failures = len([success for (success, _ignore_duration) in times if not success])
-            if failures / len(times) > failCutoff:
+            if failures * 100.0 / len(times) > self._fail_cut_off:
                 reasons.append(self._FAILED_REASON % dict(
-                        operation=operation.upper(), cutoff=failCutoff * 100))
+                        operation=operation.upper(), cutoff=self._fail_cut_off))
 
         return reasons

Added: CalendarServer/trunk/contrib/performance/loadtest/request-data/OS_X_10_6/poll_calendar_multiget.request
===================================================================
--- CalendarServer/trunk/contrib/performance/loadtest/request-data/OS_X_10_6/poll_calendar_multiget.request	                        (rev 0)
+++ CalendarServer/trunk/contrib/performance/loadtest/request-data/OS_X_10_6/poll_calendar_multiget.request	2012-04-10 15:41:35 UTC (rev 9004)
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="utf-8" ?>
+<x0:calendar-multiget xmlns:x0="urn:ietf:params:xml:ns:caldav" xmlns:x1="DAV:">
+  <x1:prop>
+    <x1:getetag/>
+    <x0:calendar-data/>
+    <x0:schedule-tag/>
+  </x1:prop>
+%(hrefs)s
+</x0:calendar-multiget>

Added: CalendarServer/trunk/contrib/performance/loadtest/request-data/OS_X_10_6/poll_calendar_multiget_hrefs.request
===================================================================
--- CalendarServer/trunk/contrib/performance/loadtest/request-data/OS_X_10_6/poll_calendar_multiget_hrefs.request	                        (rev 0)
+++ CalendarServer/trunk/contrib/performance/loadtest/request-data/OS_X_10_6/poll_calendar_multiget_hrefs.request	2012-04-10 15:41:35 UTC (rev 9004)
@@ -0,0 +1 @@
+  <x1:href>%(href)s</x1:href>

Added: CalendarServer/trunk/contrib/performance/loadtest/request-data/OS_X_10_6/poll_calendar_propfind.request
===================================================================
--- CalendarServer/trunk/contrib/performance/loadtest/request-data/OS_X_10_6/poll_calendar_propfind.request	                        (rev 0)
+++ CalendarServer/trunk/contrib/performance/loadtest/request-data/OS_X_10_6/poll_calendar_propfind.request	2012-04-10 15:41:35 UTC (rev 9004)
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<A:propfind xmlns:A="DAV:">
+  <A:prop>
+    <B:getctag xmlns:B="http://calendarserver.org/ns/"/>
+    <A:sync-token/>
+  </A:prop>
+</A:propfind>

Added: CalendarServer/trunk/contrib/performance/loadtest/request-data/OS_X_10_6/poll_calendar_propfind_d1.request
===================================================================
--- CalendarServer/trunk/contrib/performance/loadtest/request-data/OS_X_10_6/poll_calendar_propfind_d1.request	                        (rev 0)
+++ CalendarServer/trunk/contrib/performance/loadtest/request-data/OS_X_10_6/poll_calendar_propfind_d1.request	2012-04-10 15:41:35 UTC (rev 9004)
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="utf-8"?>
+<x0:propfind xmlns:x0="DAV:" xmlns:x1="http://calendarserver.org/ns/">
+ <x0:prop>
+  <x0:getetag/>
+  <x0:resourcetype/>
+  <x1:notificationtype/>
+ </x0:prop>
+</x0:propfind>

Added: CalendarServer/trunk/contrib/performance/loadtest/request-data/OS_X_10_6/poll_calendarhome_propfind.request
===================================================================
--- CalendarServer/trunk/contrib/performance/loadtest/request-data/OS_X_10_6/poll_calendarhome_propfind.request	                        (rev 0)
+++ CalendarServer/trunk/contrib/performance/loadtest/request-data/OS_X_10_6/poll_calendarhome_propfind.request	2012-04-10 15:41:35 UTC (rev 9004)
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<x0:propfind xmlns:x0="DAV:" xmlns:x3="http://apple.com/ns/ical/" xmlns:x1="http://calendarserver.org/ns/" xmlns:x2="urn:ietf:params:xml:ns:caldav">
+ <x0:prop>
+  <x1:xmpp-server/>
+  <x1:xmpp-uri/>
+  <x1:getctag/>
+  <x0:displayname/>
+  <x2:calendar-description/>
+  <x3:calendar-color/>
+  <x3:calendar-order/>
+  <x2:supported-calendar-component-set/>
+  <x0:resourcetype/>
+  <x0:owner/>
+  <x2:calendar-free-busy-set/>
+  <x2:schedule-calendar-transp/>
+  <x2:schedule-default-calendar-URL/>
+  <x0:quota-available-bytes/>
+  <x0:quota-used-bytes/>
+  <x2:calendar-timezone/>
+  <x0:current-user-privilege-set/>
+  <x1:source/>
+  <x1:subscribed-strip-alarms/>
+  <x1:subscribed-strip-attachments/>
+  <x1:subscribed-strip-todos/>
+  <x3:refreshrate/>
+  <x1:push-transports/>
+  <x1:pushkey/>
+  <x1:publish-url/>
+ </x0:prop>
+</x0:propfind>

Added: CalendarServer/trunk/contrib/performance/loadtest/request-data/OS_X_10_6/poll_notification_propfind_d1.request
===================================================================
--- CalendarServer/trunk/contrib/performance/loadtest/request-data/OS_X_10_6/poll_notification_propfind_d1.request	                        (rev 0)
+++ CalendarServer/trunk/contrib/performance/loadtest/request-data/OS_X_10_6/poll_notification_propfind_d1.request	2012-04-10 15:41:35 UTC (rev 9004)
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<A:propfind xmlns:A="DAV:">
+  <A:prop>
+    <A:getetag/>
+    <B:notificationtype xmlns:B="http://calendarserver.org/ns/"/>
+  </A:prop>
+</A:propfind>

Added: CalendarServer/trunk/contrib/performance/loadtest/request-data/OS_X_10_6/post_availability.request
===================================================================
--- CalendarServer/trunk/contrib/performance/loadtest/request-data/OS_X_10_6/post_availability.request	                        (rev 0)
+++ CalendarServer/trunk/contrib/performance/loadtest/request-data/OS_X_10_6/post_availability.request	2012-04-10 15:41:35 UTC (rev 9004)
@@ -0,0 +1,14 @@
+BEGIN:VCALENDAR
+CALSCALE:GREGORIAN
+VERSION:2.0
+METHOD:REQUEST
+PRODID:-//Apple Inc.//iCal 4.0.3//EN
+BEGIN:VFREEBUSY
+UID:%(vfreebusy-uid)s
+DTEND:%(end)s
+%(attendees)sDTSTART:%(start)s
+%(event-mask)sDTSTAMP:%(now)s
+ORGANIZER:%(organizer)s
+SUMMARY:%(summary)s
+END:VFREEBUSY
+END:VCALENDAR

Added: CalendarServer/trunk/contrib/performance/loadtest/request-data/OS_X_10_6/startup_calendar_color_proppatch.request
===================================================================
--- CalendarServer/trunk/contrib/performance/loadtest/request-data/OS_X_10_6/startup_calendar_color_proppatch.request	                        (rev 0)
+++ CalendarServer/trunk/contrib/performance/loadtest/request-data/OS_X_10_6/startup_calendar_color_proppatch.request	2012-04-10 15:41:35 UTC (rev 9004)
@@ -0,0 +1,2 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<A:propertyupdate xmlns:A="DAV:"><A:set><A:prop><E:calendar-color xmlns:E="http://apple.com/ns/ical/">#882F00FF</E:calendar-color></A:prop></A:set></A:propertyupdate>

Added: CalendarServer/trunk/contrib/performance/loadtest/request-data/OS_X_10_6/startup_calendar_order_proppatch.request
===================================================================
--- CalendarServer/trunk/contrib/performance/loadtest/request-data/OS_X_10_6/startup_calendar_order_proppatch.request	                        (rev 0)
+++ CalendarServer/trunk/contrib/performance/loadtest/request-data/OS_X_10_6/startup_calendar_order_proppatch.request	2012-04-10 15:41:35 UTC (rev 9004)
@@ -0,0 +1,2 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<A:propertyupdate xmlns:A="DAV:"><A:set><A:prop><E:calendar-order xmlns:E="http://apple.com/ns/ical/">1</E:calendar-order></A:prop></A:set></A:propertyupdate>

Added: CalendarServer/trunk/contrib/performance/loadtest/request-data/OS_X_10_6/startup_calendar_timezone_proppatch.request
===================================================================
--- CalendarServer/trunk/contrib/performance/loadtest/request-data/OS_X_10_6/startup_calendar_timezone_proppatch.request	                        (rev 0)
+++ CalendarServer/trunk/contrib/performance/loadtest/request-data/OS_X_10_6/startup_calendar_timezone_proppatch.request	2012-04-10 15:41:35 UTC (rev 9004)
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<A:propertyupdate xmlns:A="DAV:"><A:set><A:prop><C:calendar-timezone xmlns:C="urn:ietf:params:xml:ns:caldav">BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//Apple Inc.//iCal 5.0.2//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
+END:VCALENDAR
+</C:calendar-timezone></A:prop></A:set></A:propertyupdate>

Added: CalendarServer/trunk/contrib/performance/loadtest/request-data/OS_X_10_6/startup_notification_propfind.request
===================================================================
--- CalendarServer/trunk/contrib/performance/loadtest/request-data/OS_X_10_6/startup_notification_propfind.request	                        (rev 0)
+++ CalendarServer/trunk/contrib/performance/loadtest/request-data/OS_X_10_6/startup_notification_propfind.request	2012-04-10 15:41:35 UTC (rev 9004)
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="utf-8"?>
+<x0:propfind xmlns:x0="DAV:" xmlns:x1="http://calendarserver.org/ns/">
+ <x0:prop>
+  <x0:getetag/>
+  <x0:resourcetype/>
+  <x1:notificationtype/>
+ </x0:prop>
+</x0:propfind>

Added: CalendarServer/trunk/contrib/performance/loadtest/request-data/OS_X_10_6/startup_principal_expand.request
===================================================================
--- CalendarServer/trunk/contrib/performance/loadtest/request-data/OS_X_10_6/startup_principal_expand.request	                        (rev 0)
+++ CalendarServer/trunk/contrib/performance/loadtest/request-data/OS_X_10_6/startup_principal_expand.request	2012-04-10 15:41:35 UTC (rev 9004)
@@ -0,0 +1,2 @@
+<?xml version="1.0" encoding="utf-8" ?>
+<x0:expand-property xmlns:x0="DAV:"><x0:property name="calendar-proxy-write-for" namespace="http://calendarserver.org/ns/"><x0:property name="displayname"/><x0:property name="principal-URL"/><x0:property name="calendar-user-address-set" namespace="urn:ietf:params:xml:ns:caldav"/></x0:property><x0:property name="calendar-proxy-read-for" namespace="http://calendarserver.org/ns/"><x0:property name="displayname"/><x0:property name="principal-URL"/><x0:property name="calendar-user-address-set" namespace="urn:ietf:params:xml:ns:caldav"/></x0:property></x0:expand-property>

Added: CalendarServer/trunk/contrib/performance/loadtest/request-data/OS_X_10_6/startup_principal_propfind.request
===================================================================
--- CalendarServer/trunk/contrib/performance/loadtest/request-data/OS_X_10_6/startup_principal_propfind.request	                        (rev 0)
+++ CalendarServer/trunk/contrib/performance/loadtest/request-data/OS_X_10_6/startup_principal_propfind.request	2012-04-10 15:41:35 UTC (rev 9004)
@@ -0,0 +1,16 @@
+<?xml version="1.0" encoding="utf-8"?>
+<x0:propfind xmlns:x1="urn:ietf:params:xml:ns:caldav" xmlns:x0="DAV:" xmlns:x2="http://calendarserver.org/ns/">
+ <x0:prop>
+  <x0:principal-collection-set/>
+  <x1:calendar-home-set/>
+  <x1:calendar-user-address-set/>
+  <x1:schedule-inbox-URL/>
+  <x1:schedule-outbox-URL/>
+  <x2:dropbox-home-URL/>
+  <x2:xmpp-uri/>
+  <x2:notification-URL/>
+  <x0:displayname/>
+  <x0:principal-URL/>
+  <x0:supported-report-set/>
+ </x0:prop>
+</x0:propfind>

Added: CalendarServer/trunk/contrib/performance/loadtest/request-data/OS_X_10_6/startup_principal_propfind_initial.request
===================================================================
--- CalendarServer/trunk/contrib/performance/loadtest/request-data/OS_X_10_6/startup_principal_propfind_initial.request	                        (rev 0)
+++ CalendarServer/trunk/contrib/performance/loadtest/request-data/OS_X_10_6/startup_principal_propfind_initial.request	2012-04-10 15:41:35 UTC (rev 9004)
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<A:propfind xmlns:A="DAV:">
+  <A:prop>
+    <A:current-user-principal/>
+    <A:principal-URL/>
+    <A:resourcetype/>
+  </A:prop>
+</A:propfind>

Added: CalendarServer/trunk/contrib/performance/loadtest/request-data/OS_X_10_6/startup_principals_report.request
===================================================================
--- CalendarServer/trunk/contrib/performance/loadtest/request-data/OS_X_10_6/startup_principals_report.request	                        (rev 0)
+++ CalendarServer/trunk/contrib/performance/loadtest/request-data/OS_X_10_6/startup_principals_report.request	2012-04-10 15:41:35 UTC (rev 9004)
@@ -0,0 +1,2 @@
+<?xml version="1.0" encoding="utf-8" ?>
+<x0:principal-search-property-set xmlns:x0="DAV:"/>

Added: CalendarServer/trunk/contrib/performance/loadtest/request-data/OS_X_10_6/startup_well_known.request
===================================================================
--- CalendarServer/trunk/contrib/performance/loadtest/request-data/OS_X_10_6/startup_well_known.request	                        (rev 0)
+++ CalendarServer/trunk/contrib/performance/loadtest/request-data/OS_X_10_6/startup_well_known.request	2012-04-10 15:41:35 UTC (rev 9004)
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<A:propfind xmlns:A="DAV:">
+  <A:prop>
+    <A:current-user-principal/>
+    <A:principal-URL/>
+    <A:resourcetype/>
+  </A:prop>
+</A:propfind>

Added: CalendarServer/trunk/contrib/performance/loadtest/request-data/OS_X_10_6/user_list_principal_property_search.request
===================================================================
--- CalendarServer/trunk/contrib/performance/loadtest/request-data/OS_X_10_6/user_list_principal_property_search.request	                        (rev 0)
+++ CalendarServer/trunk/contrib/performance/loadtest/request-data/OS_X_10_6/user_list_principal_property_search.request	2012-04-10 15:41:35 UTC (rev 9004)
@@ -0,0 +1,2 @@
+<?xml version="1.0" encoding="utf-8" ?>
+<x0:principal-property-search xmlns:x2="urn:ietf:params:xml:ns:caldav" xmlns:x0="DAV:" xmlns:x1="http://calendarserver.org/ns/" test="anyof"><x0:property-search><x0:prop><x0:displayname/></x0:prop><x0:match match-type="starts-with">%(displayname)s</x0:match></x0:property-search><x0:property-search><x0:prop><x1:email-address-set/></x0:prop><x0:match match-type="starts-with">%(email)s</x0:match></x0:property-search><x0:property-search><x0:prop><x1:first-name/></x0:prop><x0:match match-type="starts-with">%(firstname)s</x0:match></x0:property-search><x0:property-search><x0:prop><x1:last-name/></x0:prop><x0:match match-type="starts-with">%(lastname)s</x0:match></x0:property-search><x0:prop><x1:email-address-set/><x2:calendar-user-address-set/><x2:calendar-user-type/><x0:displayname/><x1:last-name/><x1:first-name/><x1:record-type/><x0:principal-URL/></x0:prop></x0:principal-property-search>

Added: CalendarServer/trunk/contrib/performance/loadtest/request-data/OS_X_10_7/Profile
===================================================================
--- CalendarServer/trunk/contrib/performance/loadtest/request-data/OS_X_10_7/Profile	                        (rev 0)
+++ CalendarServer/trunk/contrib/performance/loadtest/request-data/OS_X_10_7/Profile	2012-04-10 15:41:35 UTC (rev 9004)
@@ -0,0 +1,64 @@
+** Initial account setup
+
+PROPFIND well-known unauthorized	- startup_well_known.request
+PROPFIND / unauthorized	            - startup_well_known.request
+HEAD well-known unauthorized
+PROPFIND / unauthorized	            - startup_well_known.request
+HEAD / unauthorized
+PROPFIND / unauthorized				- startup_well_known.request
+PROPFIND /calendar/dav/user01/user/ unauthorized	- startup_principal_propfind_initial.request
+PROPFIND /principals/users/user01/ unauthorized		- startup_principal_propfind_initial.request
+PROPFIND /principals/users/user01/					- startup_principal_propfind_initial.request
+
+PROPFIND /principals/users/user01/ unauthorized		- startup_principal_propfind.request
+PROPFIND /principals/users/user01/					- startup_principal_propfind.request
+OPTIONS /principals/__uids__/user01/
+
+PROPFIND /principals/users/user01/		            - startup_principal_propfind.request
+OPTIONS /principals/__uids__/user01/
+
+REPORT /principals/ unauthorized					- startup_principals_report.request
+REPORT /principals/									- startup_principals_report.request
+
+PROPFIND /calendars/__uids__/user01/ unauthorized	- poll_calendarhome_propfind.request
+PROPFIND /calendars/__uids__/user01/				- poll_calendarhome_propfind.request
+
+PROPPATCH /calendars/__uids__/user01/tasks/ 		- startup_calendar_color_proppatch.request
+PROPPATCH /calendars/__uids__/user01/tasks/ 		- startup_calendar_order_proppatch.request
+PROPPATCH /calendars/__uids__/user01/tasks/ 		- startup_calendar_timezone_proppatch.request
+
+PROPPATCH /calendars/__uids__/user01/calendar/ 		- startup_calendar_color_proppatch.request
+PROPPATCH /calendars/__uids__/user01/calendar/ 		- startup_calendar_order_proppatch.request
+PROPPATCH /calendars/__uids__/user01/calendar/ 		- startup_calendar_timezone_proppatch.request
+
+PROPFIND /calendars/__uids__/user01/tasks/			- poll_calendar_propfind.request
+PROPFIND /calendars/__uids__/user01/tasks/			- poll_calendar_propfind_d1.request
+
+PROPFIND /calendars/__uids__/user01/calendar/		- poll_calendar_propfind.request
+PROPFIND /calendars/__uids__/user01/calendar/		- poll_calendar_propfind_d1.request
+
+PROPFIND /calendars/__uids__/user01/inbox/			- poll_calendar_propfind.request
+PROPFIND /calendars/__uids__/user01/inbox/			- poll_calendar_propfind_d1.request
+
+PROPFIND /calendars/__uids__/user01/notification/	- poll_calendar_propfind.request
+PROPFIND /calendars/__uids__/user01/notification/	- poll_notification_propfind_d1.request
+
+REPORT /principals/__uids__/user01/					- startup_principal_expand.request
+
+	** for each delegate
+	PROPFIND /principals/__uids__/resource10/		- startup_delegate_principal_propfind.request
+	OPTIONS /principals/__uids__/user01/
+	REPORT /principals/								- startup_principals_report.request
+	
+** Polling - with sync
+PROPFIND /calendars/__uids__/user01/				- poll_calendarhome_propfind.request
+REPORT /calendars/__uids__/user01/calendar/			- poll_calendar_sync.request
+PROPFIND /calendars/__uids__/user01/calendar/		- poll_calendar_multiget.request
+	
+** Polling - without sync
+PROPFIND /calendars/__uids__/user01/				- poll_calendarhome_propfind.request
+PROPFIND /calendars/__uids__/user01/calendar/		- poll_calendar_propfind.request
+PROPFIND /calendars/__uids__/user01/calendar/		- poll_calendar_propfind_d1.request
+REPORT /calendars/__uids__/user01/calendar/			- poll_calendar_multiget.request
+PROPFIND /calendars/__uids__/user01/notification/	- poll_calendar_propfind.request
+PROPFIND /calendars/__uids__/user01/notification/	- poll_notification_propfind_d1.request

Added: CalendarServer/trunk/contrib/performance/loadtest/request-data/OS_X_10_7/poll_calendar_multiget.request
===================================================================
--- CalendarServer/trunk/contrib/performance/loadtest/request-data/OS_X_10_7/poll_calendar_multiget.request	                        (rev 0)
+++ CalendarServer/trunk/contrib/performance/loadtest/request-data/OS_X_10_7/poll_calendar_multiget.request	2012-04-10 15:41:35 UTC (rev 9004)
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<C:calendar-multiget xmlns:C="urn:ietf:params:xml:ns:caldav">
+  <A:prop xmlns:A="DAV:">
+    <A:getetag/>
+    <C:calendar-data/>
+    <C:schedule-tag/>
+  </A:prop>
+%(hrefs)s
+</C:calendar-multiget>

Added: CalendarServer/trunk/contrib/performance/loadtest/request-data/OS_X_10_7/poll_calendar_multiget_hrefs.request
===================================================================
--- CalendarServer/trunk/contrib/performance/loadtest/request-data/OS_X_10_7/poll_calendar_multiget_hrefs.request	                        (rev 0)
+++ CalendarServer/trunk/contrib/performance/loadtest/request-data/OS_X_10_7/poll_calendar_multiget_hrefs.request	2012-04-10 15:41:35 UTC (rev 9004)
@@ -0,0 +1 @@
+  <A:href xmlns:A="DAV:">%(href)s</A:href>

Added: CalendarServer/trunk/contrib/performance/loadtest/request-data/OS_X_10_7/poll_calendar_propfind.request
===================================================================
--- CalendarServer/trunk/contrib/performance/loadtest/request-data/OS_X_10_7/poll_calendar_propfind.request	                        (rev 0)
+++ CalendarServer/trunk/contrib/performance/loadtest/request-data/OS_X_10_7/poll_calendar_propfind.request	2012-04-10 15:41:35 UTC (rev 9004)
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<A:propfind xmlns:A="DAV:">
+  <A:prop>
+    <B:getctag xmlns:B="http://calendarserver.org/ns/"/>
+    <A:sync-token/>
+  </A:prop>
+</A:propfind>

Added: CalendarServer/trunk/contrib/performance/loadtest/request-data/OS_X_10_7/poll_calendar_propfind_d1.request
===================================================================
--- CalendarServer/trunk/contrib/performance/loadtest/request-data/OS_X_10_7/poll_calendar_propfind_d1.request	                        (rev 0)
+++ CalendarServer/trunk/contrib/performance/loadtest/request-data/OS_X_10_7/poll_calendar_propfind_d1.request	2012-04-10 15:41:35 UTC (rev 9004)
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<A:propfind xmlns:A="DAV:">
+  <A:prop>
+    <A:getcontenttype/>
+    <A:getetag/>
+  </A:prop>
+</A:propfind>

Added: CalendarServer/trunk/contrib/performance/loadtest/request-data/OS_X_10_7/poll_calendar_sync.request
===================================================================
--- CalendarServer/trunk/contrib/performance/loadtest/request-data/OS_X_10_7/poll_calendar_sync.request	                        (rev 0)
+++ CalendarServer/trunk/contrib/performance/loadtest/request-data/OS_X_10_7/poll_calendar_sync.request	2012-04-10 15:41:35 UTC (rev 9004)
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<A:sync-collection xmlns:A="DAV:">
+  <A:sync-token>%(sync-token)s</A:sync-token>
+  <A:prop>
+    <A:getetag/>
+    <A:getcontenttype/>
+  </A:prop>
+</A:sync-collection>

Added: CalendarServer/trunk/contrib/performance/loadtest/request-data/OS_X_10_7/poll_calendarhome_propfind.request
===================================================================
--- CalendarServer/trunk/contrib/performance/loadtest/request-data/OS_X_10_7/poll_calendarhome_propfind.request	                        (rev 0)
+++ CalendarServer/trunk/contrib/performance/loadtest/request-data/OS_X_10_7/poll_calendarhome_propfind.request	2012-04-10 15:41:35 UTC (rev 9004)
@@ -0,0 +1,39 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<A:propfind xmlns:A="DAV:">
+  <A:prop>
+    <A:add-member/>
+    <B:allowed-sharing-modes xmlns:B="http://calendarserver.org/ns/"/>
+    <D:bulk-requests xmlns:D="http://me.com/_namespace/"/>
+    <E:calendar-color xmlns:E="http://apple.com/ns/ical/"/>
+    <C:calendar-description xmlns:C="urn:ietf:params:xml:ns:caldav"/>
+    <C:calendar-free-busy-set xmlns:C="urn:ietf:params:xml:ns:caldav"/>
+    <E:calendar-order xmlns:E="http://apple.com/ns/ical/"/>
+    <C:calendar-timezone xmlns:C="urn:ietf:params:xml:ns:caldav"/>
+    <A:current-user-privilege-set/>
+    <A:displayname/>
+    <B:getctag xmlns:B="http://calendarserver.org/ns/"/>
+    <F:max-image-size xmlns:F="urn:ietf:params:xml:ns:carddav"/>
+    <F:max-resource-size xmlns:F="urn:ietf:params:xml:ns:carddav"/>
+    <B:me-card xmlns:B="http://calendarserver.org/ns/"/>
+    <A:owner/>
+    <B:publish-url xmlns:B="http://calendarserver.org/ns/"/>
+    <B:push-transports xmlns:B="http://calendarserver.org/ns/"/>
+    <B:pushkey xmlns:B="http://calendarserver.org/ns/"/>
+    <A:quota-available-bytes/>
+    <A:quota-used-bytes/>
+    <E:refreshrate xmlns:E="http://apple.com/ns/ical/"/>
+    <A:resource-id/>
+    <A:resourcetype/>
+    <C:schedule-calendar-transp xmlns:C="urn:ietf:params:xml:ns:caldav"/>
+    <C:schedule-default-calendar-URL xmlns:C="urn:ietf:params:xml:ns:caldav"/>
+    <B:source xmlns:B="http://calendarserver.org/ns/"/>
+    <B:subscribed-strip-alarms xmlns:B="http://calendarserver.org/ns/"/>
+    <B:subscribed-strip-attachments xmlns:B="http://calendarserver.org/ns/"/>
+    <B:subscribed-strip-todos xmlns:B="http://calendarserver.org/ns/"/>
+    <C:supported-calendar-component-set xmlns:C="urn:ietf:params:xml:ns:caldav"/>
+    <A:supported-report-set/>
+    <A:sync-token/>
+    <B:xmpp-server xmlns:B="http://calendarserver.org/ns/"/>
+    <B:xmpp-uri xmlns:B="http://calendarserver.org/ns/"/>
+  </A:prop>
+</A:propfind>

Added: CalendarServer/trunk/contrib/performance/loadtest/request-data/OS_X_10_7/poll_notification_propfind_d1.request
===================================================================
--- CalendarServer/trunk/contrib/performance/loadtest/request-data/OS_X_10_7/poll_notification_propfind_d1.request	                        (rev 0)
+++ CalendarServer/trunk/contrib/performance/loadtest/request-data/OS_X_10_7/poll_notification_propfind_d1.request	2012-04-10 15:41:35 UTC (rev 9004)
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<A:propfind xmlns:A="DAV:">
+  <A:prop>
+    <A:getetag/>
+    <B:notificationtype xmlns:B="http://calendarserver.org/ns/"/>
+  </A:prop>
+</A:propfind>

Added: CalendarServer/trunk/contrib/performance/loadtest/request-data/OS_X_10_7/post_availability.request
===================================================================
--- CalendarServer/trunk/contrib/performance/loadtest/request-data/OS_X_10_7/post_availability.request	                        (rev 0)
+++ CalendarServer/trunk/contrib/performance/loadtest/request-data/OS_X_10_7/post_availability.request	2012-04-10 15:41:35 UTC (rev 9004)
@@ -0,0 +1,14 @@
+BEGIN:VCALENDAR
+CALSCALE:GREGORIAN
+VERSION:2.0
+METHOD:REQUEST
+PRODID:-//Apple Inc.//iCal 4.0.3//EN
+BEGIN:VFREEBUSY
+UID:%(vfreebusy-uid)s
+DTEND:%(end)s
+%(attendees)sDTSTART:%(start)s
+%(event-mask)sDTSTAMP:%(now)s
+ORGANIZER:%(organizer)s
+SUMMARY:%(summary)s
+END:VFREEBUSY
+END:VCALENDAR

Added: CalendarServer/trunk/contrib/performance/loadtest/request-data/OS_X_10_7/startup_calendar_color_proppatch.request
===================================================================
--- CalendarServer/trunk/contrib/performance/loadtest/request-data/OS_X_10_7/startup_calendar_color_proppatch.request	                        (rev 0)
+++ CalendarServer/trunk/contrib/performance/loadtest/request-data/OS_X_10_7/startup_calendar_color_proppatch.request	2012-04-10 15:41:35 UTC (rev 9004)
@@ -0,0 +1,2 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<A:propertyupdate xmlns:A="DAV:"><A:set><A:prop><E:calendar-color xmlns:E="http://apple.com/ns/ical/">#882F00FF</E:calendar-color></A:prop></A:set></A:propertyupdate>

Added: CalendarServer/trunk/contrib/performance/loadtest/request-data/OS_X_10_7/startup_calendar_order_proppatch.request
===================================================================
--- CalendarServer/trunk/contrib/performance/loadtest/request-data/OS_X_10_7/startup_calendar_order_proppatch.request	                        (rev 0)
+++ CalendarServer/trunk/contrib/performance/loadtest/request-data/OS_X_10_7/startup_calendar_order_proppatch.request	2012-04-10 15:41:35 UTC (rev 9004)
@@ -0,0 +1,2 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<A:propertyupdate xmlns:A="DAV:"><A:set><A:prop><E:calendar-order xmlns:E="http://apple.com/ns/ical/">1</E:calendar-order></A:prop></A:set></A:propertyupdate>

Added: CalendarServer/trunk/contrib/performance/loadtest/request-data/OS_X_10_7/startup_calendar_timezone_proppatch.request
===================================================================
--- CalendarServer/trunk/contrib/performance/loadtest/request-data/OS_X_10_7/startup_calendar_timezone_proppatch.request	                        (rev 0)
+++ CalendarServer/trunk/contrib/performance/loadtest/request-data/OS_X_10_7/startup_calendar_timezone_proppatch.request	2012-04-10 15:41:35 UTC (rev 9004)
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<A:propertyupdate xmlns:A="DAV:"><A:set><A:prop><C:calendar-timezone xmlns:C="urn:ietf:params:xml:ns:caldav">BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//Apple Inc.//iCal 5.0.2//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
+END:VCALENDAR
+</C:calendar-timezone></A:prop></A:set></A:propertyupdate>

Added: CalendarServer/trunk/contrib/performance/loadtest/request-data/OS_X_10_7/startup_delegate_principal_propfind.request
===================================================================
--- CalendarServer/trunk/contrib/performance/loadtest/request-data/OS_X_10_7/startup_delegate_principal_propfind.request	                        (rev 0)
+++ CalendarServer/trunk/contrib/performance/loadtest/request-data/OS_X_10_7/startup_delegate_principal_propfind.request	2012-04-10 15:41:35 UTC (rev 9004)
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<A:propfind xmlns:A="DAV:">
+  <A:prop>
+    <B:allowed-calendar-component-set xmlns:B="http://calendarserver.org/ns/"/>
+    <C:calendar-home-set xmlns:C="urn:ietf:params:xml:ns:caldav"/>
+    <C:calendar-user-address-set xmlns:C="urn:ietf:params:xml:ns:caldav"/>
+    <A:current-user-principal/>
+    <A:displayname/>
+    <B:dropbox-home-URL xmlns:B="http://calendarserver.org/ns/"/>
+    <B:email-address-set xmlns:B="http://calendarserver.org/ns/"/>
+    <B:notification-URL xmlns:B="http://calendarserver.org/ns/"/>
+    <A:principal-collection-set/>
+    <A:principal-URL/>
+    <A:resource-id/>
+    <C:schedule-inbox-URL xmlns:C="urn:ietf:params:xml:ns:caldav"/>
+    <C:schedule-outbox-URL xmlns:C="urn:ietf:params:xml:ns:caldav"/>
+    <A:supported-report-set/>
+  </A:prop>
+</A:propfind>

Added: CalendarServer/trunk/contrib/performance/loadtest/request-data/OS_X_10_7/startup_principal_expand.request
===================================================================
--- CalendarServer/trunk/contrib/performance/loadtest/request-data/OS_X_10_7/startup_principal_expand.request	                        (rev 0)
+++ CalendarServer/trunk/contrib/performance/loadtest/request-data/OS_X_10_7/startup_principal_expand.request	2012-04-10 15:41:35 UTC (rev 9004)
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<A:expand-property xmlns:A="DAV:">
+  <A:property name="calendar-proxy-read-for" namespace="http://calendarserver.org/ns/">
+    <A:property name="calendar-user-address-set" namespace="urn:ietf:params:xml:ns:caldav"/>
+    <A:property name="email-address-set" namespace="http://calendarserver.org/ns/"/>
+    <A:property name="displayname" namespace="DAV:"/>
+  </A:property>
+  <A:property name="calendar-proxy-write-for" namespace="http://calendarserver.org/ns/">
+    <A:property name="calendar-user-address-set" namespace="urn:ietf:params:xml:ns:caldav"/>
+    <A:property name="email-address-set" namespace="http://calendarserver.org/ns/"/>
+    <A:property name="displayname" namespace="DAV:"/>
+  </A:property>
+</A:expand-property>

Added: CalendarServer/trunk/contrib/performance/loadtest/request-data/OS_X_10_7/startup_principal_propfind.request
===================================================================
--- CalendarServer/trunk/contrib/performance/loadtest/request-data/OS_X_10_7/startup_principal_propfind.request	                        (rev 0)
+++ CalendarServer/trunk/contrib/performance/loadtest/request-data/OS_X_10_7/startup_principal_propfind.request	2012-04-10 15:41:35 UTC (rev 9004)
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<A:propfind xmlns:A="DAV:">
+  <A:prop>
+    <B:allowed-calendar-component-set xmlns:B="http://calendarserver.org/ns/"/>
+    <C:calendar-home-set xmlns:C="urn:ietf:params:xml:ns:caldav"/>
+    <C:calendar-user-address-set xmlns:C="urn:ietf:params:xml:ns:caldav"/>
+    <A:current-user-principal/>
+    <A:displayname/>
+    <B:dropbox-home-URL xmlns:B="http://calendarserver.org/ns/"/>
+    <B:email-address-set xmlns:B="http://calendarserver.org/ns/"/>
+    <B:notification-URL xmlns:B="http://calendarserver.org/ns/"/>
+    <A:principal-collection-set/>
+    <A:principal-URL/>
+    <A:resource-id/>
+    <C:schedule-inbox-URL xmlns:C="urn:ietf:params:xml:ns:caldav"/>
+    <C:schedule-outbox-URL xmlns:C="urn:ietf:params:xml:ns:caldav"/>
+    <A:supported-report-set/>
+  </A:prop>
+</A:propfind>

Added: CalendarServer/trunk/contrib/performance/loadtest/request-data/OS_X_10_7/startup_principal_propfind_initial.request
===================================================================
--- CalendarServer/trunk/contrib/performance/loadtest/request-data/OS_X_10_7/startup_principal_propfind_initial.request	                        (rev 0)
+++ CalendarServer/trunk/contrib/performance/loadtest/request-data/OS_X_10_7/startup_principal_propfind_initial.request	2012-04-10 15:41:35 UTC (rev 9004)
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<A:propfind xmlns:A="DAV:">
+  <A:prop>
+    <A:current-user-principal/>
+    <A:principal-URL/>
+    <A:resourcetype/>
+  </A:prop>
+</A:propfind>

Added: CalendarServer/trunk/contrib/performance/loadtest/request-data/OS_X_10_7/startup_principals_report.request
===================================================================
--- CalendarServer/trunk/contrib/performance/loadtest/request-data/OS_X_10_7/startup_principals_report.request	                        (rev 0)
+++ CalendarServer/trunk/contrib/performance/loadtest/request-data/OS_X_10_7/startup_principals_report.request	2012-04-10 15:41:35 UTC (rev 9004)
@@ -0,0 +1,2 @@
+<?xml version="1.0" encoding="utf-8" ?>
+<x0:principal-search-property-set xmlns:x0="DAV:"/>

Added: CalendarServer/trunk/contrib/performance/loadtest/request-data/OS_X_10_7/startup_well_known.request
===================================================================
--- CalendarServer/trunk/contrib/performance/loadtest/request-data/OS_X_10_7/startup_well_known.request	                        (rev 0)
+++ CalendarServer/trunk/contrib/performance/loadtest/request-data/OS_X_10_7/startup_well_known.request	2012-04-10 15:41:35 UTC (rev 9004)
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<A:propfind xmlns:A="DAV:">
+  <A:prop>
+    <A:current-user-principal/>
+    <A:principal-URL/>
+    <A:resourcetype/>
+  </A:prop>
+</A:propfind>

Added: CalendarServer/trunk/contrib/performance/loadtest/request-data/OS_X_10_7/user_list_principal_property_search.request
===================================================================
--- CalendarServer/trunk/contrib/performance/loadtest/request-data/OS_X_10_7/user_list_principal_property_search.request	                        (rev 0)
+++ CalendarServer/trunk/contrib/performance/loadtest/request-data/OS_X_10_7/user_list_principal_property_search.request	2012-04-10 15:41:35 UTC (rev 9004)
@@ -0,0 +1,2 @@
+<?xml version="1.0" encoding="utf-8" ?>
+<x0:principal-property-search xmlns:x2="urn:ietf:params:xml:ns:caldav" xmlns:x0="DAV:" xmlns:x1="http://calendarserver.org/ns/" test="anyof"><x0:property-search><x0:prop><x0:displayname/></x0:prop><x0:match match-type="starts-with">%(displayname)s</x0:match></x0:property-search><x0:property-search><x0:prop><x1:email-address-set/></x0:prop><x0:match match-type="starts-with">%(email)s</x0:match></x0:property-search><x0:property-search><x0:prop><x1:first-name/></x0:prop><x0:match match-type="starts-with">%(firstname)s</x0:match></x0:property-search><x0:property-search><x0:prop><x1:last-name/></x0:prop><x0:match match-type="starts-with">%(lastname)s</x0:match></x0:property-search><x0:prop><x1:email-address-set/><x2:calendar-user-address-set/><x2:calendar-user-type/><x0:displayname/><x1:last-name/><x1:first-name/><x1:record-type/><x0:principal-URL/></x0:prop></x0:principal-property-search>

Added: CalendarServer/trunk/contrib/performance/loadtest/request-data/iOS_5/Profile
===================================================================
--- CalendarServer/trunk/contrib/performance/loadtest/request-data/iOS_5/Profile	                        (rev 0)
+++ CalendarServer/trunk/contrib/performance/loadtest/request-data/iOS_5/Profile	2012-04-10 15:41:35 UTC (rev 9004)
@@ -0,0 +1,44 @@
+** Initial account setup
+
+PROPFIND well-known unauthorized	- startup_well_known.request
+PROPFIND / unauthorized	            - startup_well_known.request
+PROPFIND /principals/ unauthorized	            - startup_principal_propfind_initial.request
+PROPFIND /principals/ authorized	            - startup_principal_propfind_initial.request
+
+OPTIONS /principals/__uids__/user01/ unauthorized
+OPTIONS /principals/__uids__/user01/ authorized
+
+PROPFIND /principals/users/user01/ unauthorized		- startup_principal_propfind.request
+PROPFIND /principals/users/user01/					- startup_principal_propfind.request
+
+REPORT /principals/ unauthorized					- startup_principals_report.request
+REPORT /principals/									- startup_principals_report.request
+
+PROPFIND /calendars/__uids__/user01/ unauthorized	- poll_calendarhome_propfind.request
+PROPFIND /calendars/__uids__/user01/				- poll_calendarhome_propfind.request
+
+PROPPATCH /calendars/__uids__/user01/tasks/ 		- startup_calendar_color_proppatch.request
+PROPPATCH /calendars/__uids__/user01/tasks/ 		- startup_calendar_order_proppatch.request
+
+PROPPATCH /calendars/__uids__/user01/calendar/ 		- startup_calendar_color_proppatch.request
+PROPPATCH /calendars/__uids__/user01/calendar/ 		- startup_calendar_order_proppatch.request
+
+PROPFIND /calendars/__uids__/user01/tasks/			- poll_calendar_propfind.request
+PROPFIND /calendars/__uids__/user01/tasks/			- poll_calendar_vtodo_query.request
+
+PROPFIND /calendars/__uids__/user01/calendar/		- poll_calendar_propfind.request
+PROPFIND /calendars/__uids__/user01/calendar/		- poll_calendar_vevent_tr_query.request
+
+PROPFIND /calendars/__uids__/user01/inbox/			- poll_calendar_propfind.request
+PROPFIND /calendars/__uids__/user01/inbox/			- poll_calendar_propfind_d1.request
+	
+** Polling - without sync
+PROPFIND /calendars/__uids__/user01/				- poll_calendarhome_propfind.request
+
+PROPFIND /calendars/__uids__/user01/calendar/		- poll_calendar_propfind.request
+REPORT /calendars/__uids__/user01/calendar/			- poll_calendar_propfind_d1.request
+REPORT /calendars/__uids__/user01/calendar/			- poll_calendar_multiget.request
+
+PROPFIND /calendars/__uids__/user01/calendar/		- poll_calendar_propfind.request
+REPORT /calendars/__uids__/user01/tasks/			- poll_calendar_vevent_tr_query.request
+REPORT /calendars/__uids__/user01/tasks/			- poll_calendar_vtodo_query.request

Added: CalendarServer/trunk/contrib/performance/loadtest/request-data/iOS_5/poll_calendar_multiget.request
===================================================================
--- CalendarServer/trunk/contrib/performance/loadtest/request-data/iOS_5/poll_calendar_multiget.request	                        (rev 0)
+++ CalendarServer/trunk/contrib/performance/loadtest/request-data/iOS_5/poll_calendar_multiget.request	2012-04-10 15:41:35 UTC (rev 9004)
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<G:calendar-multiget xmlns:G="urn:ietf:params:xml:ns:caldav">
+  <A:prop xmlns:A="DAV:">
+    <A:getetag/>
+    <G:calendar-data/>
+    <G:schedule-tag/>
+  </A:prop>
+%(hrefs)s
+</G:calendar-multiget>

Added: CalendarServer/trunk/contrib/performance/loadtest/request-data/iOS_5/poll_calendar_multiget_hrefs.request
===================================================================
--- CalendarServer/trunk/contrib/performance/loadtest/request-data/iOS_5/poll_calendar_multiget_hrefs.request	                        (rev 0)
+++ CalendarServer/trunk/contrib/performance/loadtest/request-data/iOS_5/poll_calendar_multiget_hrefs.request	2012-04-10 15:41:35 UTC (rev 9004)
@@ -0,0 +1 @@
+  <A:href xmlns:A="DAV:">%(href)s</A:href>

Added: CalendarServer/trunk/contrib/performance/loadtest/request-data/iOS_5/poll_calendar_propfind.request
===================================================================
--- CalendarServer/trunk/contrib/performance/loadtest/request-data/iOS_5/poll_calendar_propfind.request	                        (rev 0)
+++ CalendarServer/trunk/contrib/performance/loadtest/request-data/iOS_5/poll_calendar_propfind.request	2012-04-10 15:41:35 UTC (rev 9004)
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<A:propfind xmlns:A="DAV:">
+  <A:prop>
+    <C:getctag xmlns:C="http://calendarserver.org/ns/"/>
+    <A:sync-token/>
+  </A:prop>
+</A:propfind>

Added: CalendarServer/trunk/contrib/performance/loadtest/request-data/iOS_5/poll_calendar_propfind_d1.request
===================================================================
--- CalendarServer/trunk/contrib/performance/loadtest/request-data/iOS_5/poll_calendar_propfind_d1.request	                        (rev 0)
+++ CalendarServer/trunk/contrib/performance/loadtest/request-data/iOS_5/poll_calendar_propfind_d1.request	2012-04-10 15:41:35 UTC (rev 9004)
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<A:propfind xmlns:A="DAV:">
+  <A:prop>
+    <A:getcontenttype/>
+    <A:getetag/>
+  </A:prop>
+</A:propfind>

Added: CalendarServer/trunk/contrib/performance/loadtest/request-data/iOS_5/poll_calendar_vevent_tr_query.request
===================================================================
--- CalendarServer/trunk/contrib/performance/loadtest/request-data/iOS_5/poll_calendar_vevent_tr_query.request	                        (rev 0)
+++ CalendarServer/trunk/contrib/performance/loadtest/request-data/iOS_5/poll_calendar_vevent_tr_query.request	2012-04-10 15:41:35 UTC (rev 9004)
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<G:calendar-query xmlns:G="urn:ietf:params:xml:ns:caldav">
+  <A:prop xmlns:A="DAV:">
+    <A:getetag/>
+    <A:getcontenttype/>
+  </A:prop>
+  <G:filter>
+    <G:comp-filter name="VCALENDAR">
+      <G:comp-filter name="VEVENT">
+        <G:time-range start="%(start-date)sT000000Z"/>
+      </G:comp-filter>
+    </G:comp-filter>
+  </G:filter>
+</G:calendar-query>

Added: CalendarServer/trunk/contrib/performance/loadtest/request-data/iOS_5/poll_calendar_vtodo_query.request
===================================================================
--- CalendarServer/trunk/contrib/performance/loadtest/request-data/iOS_5/poll_calendar_vtodo_query.request	                        (rev 0)
+++ CalendarServer/trunk/contrib/performance/loadtest/request-data/iOS_5/poll_calendar_vtodo_query.request	2012-04-10 15:41:35 UTC (rev 9004)
@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<G:calendar-query xmlns:G="urn:ietf:params:xml:ns:caldav">
+  <A:prop xmlns:A="DAV:">
+    <A:getetag/>
+    <A:getcontenttype/>
+  </A:prop>
+  <G:filter>
+    <G:comp-filter name="VCALENDAR">
+      <G:comp-filter name="VTODO"/>
+    </G:comp-filter>
+  </G:filter>
+</G:calendar-query>

Added: CalendarServer/trunk/contrib/performance/loadtest/request-data/iOS_5/poll_calendarhome_propfind.request
===================================================================
--- CalendarServer/trunk/contrib/performance/loadtest/request-data/iOS_5/poll_calendarhome_propfind.request	                        (rev 0)
+++ CalendarServer/trunk/contrib/performance/loadtest/request-data/iOS_5/poll_calendarhome_propfind.request	2012-04-10 15:41:35 UTC (rev 9004)
@@ -0,0 +1,39 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<A:propfind xmlns:A="DAV:">
+  <A:prop>
+    <A:add-member/>
+    <C:allowed-sharing-modes xmlns:C="http://calendarserver.org/ns/"/>
+    <E:bulk-requests xmlns:E="http://me.com/_namespace/"/>
+    <H:calendar-color xmlns:H="http://apple.com/ns/ical/"/>
+    <G:calendar-description xmlns:G="urn:ietf:params:xml:ns:caldav"/>
+    <G:calendar-free-busy-set xmlns:G="urn:ietf:params:xml:ns:caldav"/>
+    <H:calendar-order xmlns:H="http://apple.com/ns/ical/"/>
+    <G:calendar-timezone xmlns:G="urn:ietf:params:xml:ns:caldav"/>
+    <A:current-user-privilege-set/>
+    <A:displayname/>
+    <C:getctag xmlns:C="http://calendarserver.org/ns/"/>
+    <B:max-image-size xmlns:B="urn:ietf:params:xml:ns:carddav"/>
+    <B:max-resource-size xmlns:B="urn:ietf:params:xml:ns:carddav"/>
+    <C:me-card xmlns:C="http://calendarserver.org/ns/"/>
+    <A:owner/>
+    <C:publish-url xmlns:C="http://calendarserver.org/ns/"/>
+    <C:push-transports xmlns:C="http://calendarserver.org/ns/"/>
+    <C:pushkey xmlns:C="http://calendarserver.org/ns/"/>
+    <A:quota-available-bytes/>
+    <A:quota-used-bytes/>
+    <H:refreshrate xmlns:H="http://apple.com/ns/ical/"/>
+    <A:resource-id/>
+    <A:resourcetype/>
+    <G:schedule-calendar-transp xmlns:G="urn:ietf:params:xml:ns:caldav"/>
+    <G:schedule-default-calendar-URL xmlns:G="urn:ietf:params:xml:ns:caldav"/>
+    <C:source xmlns:C="http://calendarserver.org/ns/"/>
+    <C:subscribed-strip-alarms xmlns:C="http://calendarserver.org/ns/"/>
+    <C:subscribed-strip-attachments xmlns:C="http://calendarserver.org/ns/"/>
+    <C:subscribed-strip-todos xmlns:C="http://calendarserver.org/ns/"/>
+    <G:supported-calendar-component-set xmlns:G="urn:ietf:params:xml:ns:caldav"/>
+    <A:supported-report-set/>
+    <A:sync-token/>
+    <C:xmpp-server xmlns:C="http://calendarserver.org/ns/"/>
+    <C:xmpp-uri xmlns:C="http://calendarserver.org/ns/"/>
+  </A:prop>
+</A:propfind>

Added: CalendarServer/trunk/contrib/performance/loadtest/request-data/iOS_5/startup_calendar_color_proppatch.request
===================================================================
--- CalendarServer/trunk/contrib/performance/loadtest/request-data/iOS_5/startup_calendar_color_proppatch.request	                        (rev 0)
+++ CalendarServer/trunk/contrib/performance/loadtest/request-data/iOS_5/startup_calendar_color_proppatch.request	2012-04-10 15:41:35 UTC (rev 9004)
@@ -0,0 +1,2 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<A:propertyupdate xmlns:A="DAV:"><A:set><A:prop><H:calendar-color xmlns:H="http://apple.com/ns/ical/">#711A76</H:calendar-color></A:prop></A:set></A:propertyupdate>

Added: CalendarServer/trunk/contrib/performance/loadtest/request-data/iOS_5/startup_calendar_order_proppatch.request
===================================================================
--- CalendarServer/trunk/contrib/performance/loadtest/request-data/iOS_5/startup_calendar_order_proppatch.request	                        (rev 0)
+++ CalendarServer/trunk/contrib/performance/loadtest/request-data/iOS_5/startup_calendar_order_proppatch.request	2012-04-10 15:41:35 UTC (rev 9004)
@@ -0,0 +1,2 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<A:propertyupdate xmlns:A="DAV:"><A:set><A:prop><H:calendar-order xmlns:H="http://apple.com/ns/ical/">0</H:calendar-order></A:prop></A:set></A:propertyupdate>

Added: CalendarServer/trunk/contrib/performance/loadtest/request-data/iOS_5/startup_principal_propfind.request
===================================================================
--- CalendarServer/trunk/contrib/performance/loadtest/request-data/iOS_5/startup_principal_propfind.request	                        (rev 0)
+++ CalendarServer/trunk/contrib/performance/loadtest/request-data/iOS_5/startup_principal_propfind.request	2012-04-10 15:41:35 UTC (rev 9004)
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<A:propfind xmlns:A="DAV:">
+  <A:prop>
+    <C:allowed-calendar-component-set xmlns:C="http://calendarserver.org/ns/"/>
+    <G:calendar-home-set xmlns:G="urn:ietf:params:xml:ns:caldav"/>
+    <G:calendar-user-address-set xmlns:G="urn:ietf:params:xml:ns:caldav"/>
+    <A:current-user-principal/>
+    <A:displayname/>
+    <C:dropbox-home-URL xmlns:C="http://calendarserver.org/ns/"/>
+    <C:email-address-set xmlns:C="http://calendarserver.org/ns/"/>
+    <C:notification-URL xmlns:C="http://calendarserver.org/ns/"/>
+    <A:principal-collection-set/>
+    <A:principal-URL/>
+    <A:resource-id/>
+    <G:schedule-inbox-URL xmlns:G="urn:ietf:params:xml:ns:caldav"/>
+    <G:schedule-outbox-URL xmlns:G="urn:ietf:params:xml:ns:caldav"/>
+    <A:supported-report-set/>
+  </A:prop>
+</A:propfind>

Added: CalendarServer/trunk/contrib/performance/loadtest/request-data/iOS_5/startup_principal_propfind_initial.request
===================================================================
--- CalendarServer/trunk/contrib/performance/loadtest/request-data/iOS_5/startup_principal_propfind_initial.request	                        (rev 0)
+++ CalendarServer/trunk/contrib/performance/loadtest/request-data/iOS_5/startup_principal_propfind_initial.request	2012-04-10 15:41:35 UTC (rev 9004)
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<A:propfind xmlns:A="DAV:">
+  <A:prop>
+    <A:current-user-principal/>
+    <A:principal-URL/>
+    <A:resourcetype/>
+  </A:prop>
+</A:propfind>

Added: CalendarServer/trunk/contrib/performance/loadtest/request-data/iOS_5/startup_principals_report.request
===================================================================
--- CalendarServer/trunk/contrib/performance/loadtest/request-data/iOS_5/startup_principals_report.request	                        (rev 0)
+++ CalendarServer/trunk/contrib/performance/loadtest/request-data/iOS_5/startup_principals_report.request	2012-04-10 15:41:35 UTC (rev 9004)
@@ -0,0 +1,2 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<A:principal-search-property-set xmlns:A="DAV:"/>

Added: CalendarServer/trunk/contrib/performance/loadtest/request-data/iOS_5/startup_well_known.request
===================================================================
--- CalendarServer/trunk/contrib/performance/loadtest/request-data/iOS_5/startup_well_known.request	                        (rev 0)
+++ CalendarServer/trunk/contrib/performance/loadtest/request-data/iOS_5/startup_well_known.request	2012-04-10 15:41:35 UTC (rev 9004)
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<A:propfind xmlns:A="DAV:">
+  <A:prop>
+    <A:current-user-principal/>
+    <A:principal-URL/>
+    <A:resourcetype/>
+  </A:prop>
+</A:propfind>

Deleted: CalendarServer/trunk/contrib/performance/loadtest/request-data/sl_calendar_propfind.request
===================================================================
--- CalendarServer/trunk/contrib/performance/loadtest/request-data/sl_calendar_propfind.request	2012-04-10 00:41:41 UTC (rev 9003)
+++ CalendarServer/trunk/contrib/performance/loadtest/request-data/sl_calendar_propfind.request	2012-04-10 15:41:35 UTC (rev 9004)
@@ -1,8 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<x0:propfind xmlns:x0="DAV:" xmlns:x1="http://calendarserver.org/ns/">
- <x0:prop>
-  <x0:getetag/>
-  <x0:resourcetype/>
-  <x1:notificationtype/>
- </x0:prop>
-</x0:propfind>

Deleted: CalendarServer/trunk/contrib/performance/loadtest/request-data/sl_calendar_report.request
===================================================================
--- CalendarServer/trunk/contrib/performance/loadtest/request-data/sl_calendar_report.request	2012-04-10 00:41:41 UTC (rev 9003)
+++ CalendarServer/trunk/contrib/performance/loadtest/request-data/sl_calendar_report.request	2012-04-10 15:41:35 UTC (rev 9004)
@@ -1,9 +0,0 @@
-<?xml version="1.0" encoding="utf-8" ?>
-<x0:calendar-multiget xmlns:x0="urn:ietf:params:xml:ns:caldav" xmlns:x1="DAV:">
-  <x1:prop>
-    <x1:getetag/>
-    <x0:calendar-data/>
-    <x0:schedule-tag/>
-  </x1:prop>
-%(hrefs)s
-</x0:calendar-multiget>

Deleted: CalendarServer/trunk/contrib/performance/loadtest/request-data/sl_calendar_report_href.request
===================================================================
--- CalendarServer/trunk/contrib/performance/loadtest/request-data/sl_calendar_report_href.request	2012-04-10 00:41:41 UTC (rev 9003)
+++ CalendarServer/trunk/contrib/performance/loadtest/request-data/sl_calendar_report_href.request	2012-04-10 15:41:35 UTC (rev 9004)
@@ -1 +0,0 @@
-  <x1:href>%(href)s</x1:href>

Deleted: CalendarServer/trunk/contrib/performance/loadtest/request-data/sl_post_availability.request
===================================================================
--- CalendarServer/trunk/contrib/performance/loadtest/request-data/sl_post_availability.request	2012-04-10 00:41:41 UTC (rev 9003)
+++ CalendarServer/trunk/contrib/performance/loadtest/request-data/sl_post_availability.request	2012-04-10 15:41:35 UTC (rev 9004)
@@ -1,14 +0,0 @@
-BEGIN:VCALENDAR
-CALSCALE:GREGORIAN
-VERSION:2.0
-METHOD:REQUEST
-PRODID:-//Apple Inc.//iCal 4.0.3//EN
-BEGIN:VFREEBUSY
-UID:%(vfreebusy-uid)s
-DTEND:%(end)s
-%(attendees)sDTSTART:%(start)s
-%(event-mask)sDTSTAMP:%(now)s
-ORGANIZER:%(organizer)s
-SUMMARY:%(summary)s
-END:VFREEBUSY
-END:VCALENDAR

Deleted: CalendarServer/trunk/contrib/performance/loadtest/request-data/sl_startup_calendarhome_propfind.request
===================================================================
--- CalendarServer/trunk/contrib/performance/loadtest/request-data/sl_startup_calendarhome_propfind.request	2012-04-10 00:41:41 UTC (rev 9003)
+++ CalendarServer/trunk/contrib/performance/loadtest/request-data/sl_startup_calendarhome_propfind.request	2012-04-10 15:41:35 UTC (rev 9004)
@@ -1,30 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<x0:propfind xmlns:x0="DAV:" xmlns:x3="http://apple.com/ns/ical/" xmlns:x1="http://calendarserver.org/ns/" xmlns:x2="urn:ietf:params:xml:ns:caldav">
- <x0:prop>
-  <x1:xmpp-server/>
-  <x1:xmpp-uri/>
-  <x1:getctag/>
-  <x0:displayname/>
-  <x2:calendar-description/>
-  <x3:calendar-color/>
-  <x3:calendar-order/>
-  <x2:supported-calendar-component-set/>
-  <x0:resourcetype/>
-  <x0:owner/>
-  <x2:calendar-free-busy-set/>
-  <x2:schedule-calendar-transp/>
-  <x2:schedule-default-calendar-URL/>
-  <x0:quota-available-bytes/>
-  <x0:quota-used-bytes/>
-  <x2:calendar-timezone/>
-  <x0:current-user-privilege-set/>
-  <x1:source/>
-  <x1:subscribed-strip-alarms/>
-  <x1:subscribed-strip-attachments/>
-  <x1:subscribed-strip-todos/>
-  <x3:refreshrate/>
-  <x1:push-transports/>
-  <x1:pushkey/>
-  <x1:publish-url/>
- </x0:prop>
-</x0:propfind>

Deleted: CalendarServer/trunk/contrib/performance/loadtest/request-data/sl_startup_notification_propfind.request
===================================================================
--- CalendarServer/trunk/contrib/performance/loadtest/request-data/sl_startup_notification_propfind.request	2012-04-10 00:41:41 UTC (rev 9003)
+++ CalendarServer/trunk/contrib/performance/loadtest/request-data/sl_startup_notification_propfind.request	2012-04-10 15:41:35 UTC (rev 9004)
@@ -1,8 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<x0:propfind xmlns:x0="DAV:" xmlns:x1="http://calendarserver.org/ns/">
- <x0:prop>
-  <x0:getetag/>
-  <x0:resourcetype/>
-  <x1:notificationtype/>
- </x0:prop>
-</x0:propfind>

Deleted: CalendarServer/trunk/contrib/performance/loadtest/request-data/sl_startup_principal_propfind.request
===================================================================
--- CalendarServer/trunk/contrib/performance/loadtest/request-data/sl_startup_principal_propfind.request	2012-04-10 00:41:41 UTC (rev 9003)
+++ CalendarServer/trunk/contrib/performance/loadtest/request-data/sl_startup_principal_propfind.request	2012-04-10 15:41:35 UTC (rev 9004)
@@ -1,16 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<x0:propfind xmlns:x1="urn:ietf:params:xml:ns:caldav" xmlns:x0="DAV:" xmlns:x2="http://calendarserver.org/ns/">
- <x0:prop>
-  <x0:principal-collection-set/>
-  <x1:calendar-home-set/>
-  <x1:calendar-user-address-set/>
-  <x1:schedule-inbox-URL/>
-  <x1:schedule-outbox-URL/>
-  <x2:dropbox-home-URL/>
-  <x2:xmpp-uri/>
-  <x2:notification-URL/>
-  <x0:displayname/>
-  <x0:principal-URL/>
-  <x0:supported-report-set/>
- </x0:prop>
-</x0:propfind>

Deleted: CalendarServer/trunk/contrib/performance/loadtest/request-data/sl_startup_principal_propfind_initial.request
===================================================================
--- CalendarServer/trunk/contrib/performance/loadtest/request-data/sl_startup_principal_propfind_initial.request	2012-04-10 00:41:41 UTC (rev 9003)
+++ CalendarServer/trunk/contrib/performance/loadtest/request-data/sl_startup_principal_propfind_initial.request	2012-04-10 15:41:35 UTC (rev 9004)
@@ -1,8 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<A:propfind xmlns:A="DAV:">
-  <A:prop>
-    <A:current-user-principal/>
-    <A:principal-URL/>
-    <A:resourcetype/>
-  </A:prop>
-</A:propfind>

Deleted: CalendarServer/trunk/contrib/performance/loadtest/request-data/sl_startup_principal_report.request
===================================================================
--- CalendarServer/trunk/contrib/performance/loadtest/request-data/sl_startup_principal_report.request	2012-04-10 00:41:41 UTC (rev 9003)
+++ CalendarServer/trunk/contrib/performance/loadtest/request-data/sl_startup_principal_report.request	2012-04-10 15:41:35 UTC (rev 9004)
@@ -1,2 +0,0 @@
-<?xml version="1.0" encoding="utf-8" ?>
-<x0:expand-property xmlns:x0="DAV:"><x0:property name="calendar-proxy-write-for" namespace="http://calendarserver.org/ns/"><x0:property name="displayname"/><x0:property name="principal-URL"/><x0:property name="calendar-user-address-set" namespace="urn:ietf:params:xml:ns:caldav"/></x0:property><x0:property name="calendar-proxy-read-for" namespace="http://calendarserver.org/ns/"><x0:property name="displayname"/><x0:property name="principal-URL"/><x0:property name="calendar-user-address-set" namespace="urn:ietf:params:xml:ns:caldav"/></x0:property></x0:expand-property>

Deleted: CalendarServer/trunk/contrib/performance/loadtest/request-data/sl_startup_principals_report.request
===================================================================
--- CalendarServer/trunk/contrib/performance/loadtest/request-data/sl_startup_principals_report.request	2012-04-10 00:41:41 UTC (rev 9003)
+++ CalendarServer/trunk/contrib/performance/loadtest/request-data/sl_startup_principals_report.request	2012-04-10 15:41:35 UTC (rev 9004)
@@ -1,2 +0,0 @@
-<?xml version="1.0" encoding="utf-8" ?>
-<x0:principal-search-property-set xmlns:x0="DAV:"/>

Deleted: CalendarServer/trunk/contrib/performance/loadtest/request-data/sl_user_list_principal_property_search.request
===================================================================
--- CalendarServer/trunk/contrib/performance/loadtest/request-data/sl_user_list_principal_property_search.request	2012-04-10 00:41:41 UTC (rev 9003)
+++ CalendarServer/trunk/contrib/performance/loadtest/request-data/sl_user_list_principal_property_search.request	2012-04-10 15:41:35 UTC (rev 9004)
@@ -1,2 +0,0 @@
-<?xml version="1.0" encoding="utf-8" ?>
-<x0:principal-property-search xmlns:x2="urn:ietf:params:xml:ns:caldav" xmlns:x0="DAV:" xmlns:x1="http://calendarserver.org/ns/" test="anyof"><x0:property-search><x0:prop><x0:displayname/></x0:prop><x0:match match-type="starts-with">%(displayname)s</x0:match></x0:property-search><x0:property-search><x0:prop><x1:email-address-set/></x0:prop><x0:match match-type="starts-with">%(email)s</x0:match></x0:property-search><x0:property-search><x0:prop><x1:first-name/></x0:prop><x0:match match-type="starts-with">%(firstname)s</x0:match></x0:property-search><x0:property-search><x0:prop><x1:last-name/></x0:prop><x0:match match-type="starts-with">%(lastname)s</x0:match></x0:property-search><x0:prop><x1:email-address-set/><x2:calendar-user-address-set/><x2:calendar-user-type/><x0:displayname/><x1:last-name/><x1:first-name/><x1:record-type/><x0:principal-URL/></x0:prop></x0:principal-property-search>

Modified: CalendarServer/trunk/contrib/performance/loadtest/sim.py
===================================================================
--- CalendarServer/trunk/contrib/performance/loadtest/sim.py	2012-04-10 00:41:41 UTC (rev 9003)
+++ CalendarServer/trunk/contrib/performance/loadtest/sim.py	2012-04-10 15:41:35 UTC (rev 9004)
@@ -1,6 +1,6 @@
 # -*- test-case-name: contrib.performance.loadtest.test_sim -*-
 ##
-# Copyright (c) 2011 Apple Inc. All rights reserved.
+# Copyright (c) 2011-2012 Apple Inc. All rights reserved.
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
 # you may not use this file except in compliance with the License.
@@ -34,7 +34,7 @@
 
 from twisted.internet.protocol import ProcessProtocol
 
-from contrib.performance.loadtest.ical import SnowLeopard
+from contrib.performance.loadtest.ical import OS_X_10_6
 from contrib.performance.loadtest.profiles import Eventer, Inviter, Accepter
 from contrib.performance.loadtest.population import (
     Populator, ProfileType, ClientType, PopulationParameters, SmoothRampUp,
@@ -260,7 +260,7 @@
                              for profile in clientConfig["profiles"]]))
             if not parameters.clients:
                 parameters.addClient(1,
-                                     ClientType(SnowLeopard, {},
+                                     ClientType(OS_X_10_6, {},
                                                 [Eventer, Inviter, Accepter]))
         else:
             # Manager / observer process.
@@ -443,11 +443,11 @@
             obs.report()
             failures.extend(obs.failures())
         if failures:
-            self.output.write('FAIL\n')
+            self.output.write('\n*** FAIL\n')
             self.output.write('\n'.join(failures))
             self.output.write('\n')
         else:
-            self.output.write('PASS\n')
+            self.output.write('\n*** PASS\n')
 
 
 

Modified: CalendarServer/trunk/contrib/performance/loadtest/test_ical.py
===================================================================
--- CalendarServer/trunk/contrib/performance/loadtest/test_ical.py	2012-04-10 00:41:41 UTC (rev 9003)
+++ CalendarServer/trunk/contrib/performance/loadtest/test_ical.py	2012-04-10 15:41:35 UTC (rev 9004)
@@ -1,5 +1,5 @@
 ##
-# Copyright (c) 2010 Apple Inc. All rights reserved.
+# Copyright (c) 2010-2012 Apple Inc. All rights reserved.
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
 # you may not use this file except in compliance with the License.
@@ -28,7 +28,7 @@
 from caldavclientlibrary.protocol.caldav.definitions import caldavxml
 from caldavclientlibrary.protocol.caldav.definitions import csxml
 
-from contrib.performance.loadtest.ical import XMPPPush, Event, Calendar, SnowLeopard
+from contrib.performance.loadtest.ical import XMPPPush, Event, Calendar, OS_X_10_6
 from contrib.performance.loadtest.sim import _DirectoryRecord
 from contrib.performance.httpclient import MemoryConsumer, StringProducer
 from twistedcaldav.ical import Component
@@ -1146,20 +1146,20 @@
 
 
 
-class SnowLeopardMixin:
+class OS_X_10_6Mixin:
     """
-    Mixin for L{TestCase}s for L{SnowLeopard}.
+    Mixin for L{TestCase}s for L{OS_X_10_6}.
     """
     def setUp(self):
         TimezoneCache.create()
         self.record = _DirectoryRecord(
             u"user91", u"user91", u"User 91", u"user91 at example.org")
-        self.client = SnowLeopard(None, "http://127.0.0.1/", self.record, None)
+        self.client = OS_X_10_6(None, "http://127.0.0.1/", self.record, None)
 
 
     def interceptRequests(self):
         requests = []
-        def request(*args):
+        def request(*args, **kwargs):
             result = Deferred()
             requests.append((result, args))
             return result
@@ -1168,9 +1168,9 @@
 
 
 
-class SnowLeopardTests(SnowLeopardMixin, TestCase):
+class OS_X_10_6Tests(OS_X_10_6Mixin, TestCase):
     """
-    Tests for L{SnowLeopard}.
+    Tests for L{OS_X_10_6}.
     """
     def test_parsePrincipalPROPFINDResponse(self):
         """
@@ -1211,52 +1211,39 @@
 
     def test_extractCalendars(self):
         """
-        L{SnowLeopard._extractCalendars} accepts a calendar home
+        L{OS_X_10_6._extractCalendars} accepts a calendar home
         PROPFIND response body and returns a list of calendar objects
         constructed from the data extracted from the response.
         """
         home = "/calendars/__uids__/user01/"
         calendars = self.client._extractCalendars(
-            CALENDAR_HOME_PROPFIND_RESPONSE, home)
+            self.client._parseMultiStatus(CALENDAR_HOME_PROPFIND_RESPONSE), home)
         calendars.sort(key=lambda cal: cal.resourceType)
-        dropbox, notification, calendar, inbox, outbox = calendars
+        calendar, inbox = calendars
 
-        self.assertEquals(dropbox.resourceType, csxml.dropbox_home)
-        self.assertEquals(dropbox.name, None)
-        self.assertEquals(dropbox.url, "/calendars/__uids__/user01/dropbox/")
-        self.assertEquals(dropbox.ctag, None)
-
-        self.assertEquals(notification.resourceType, csxml.notification)
-        self.assertEquals(notification.name, "notification")
-        self.assertEquals(notification.url, "/calendars/__uids__/user01/notification/")
-        self.assertEquals(notification.ctag, None)
-
         self.assertEquals(calendar.resourceType, caldavxml.calendar)
         self.assertEquals(calendar.name, "calendar")
         self.assertEquals(calendar.url, "/calendars/__uids__/user01/calendar/")
-        self.assertEquals(calendar.ctag, "c2696540-4c4c-4a31-adaf-c99630776828#3")
+        self.assertEquals(calendar.changeToken, "c2696540-4c4c-4a31-adaf-c99630776828#3")
 
         self.assertEquals(inbox.resourceType, caldavxml.schedule_inbox)
         self.assertEquals(inbox.name, "inbox")
         self.assertEquals(inbox.url, "/calendars/__uids__/user01/inbox/")
-        self.assertEquals(inbox.ctag, "a483dab3-1391-445b-b1c3-5ae9dfc81c2f#0")
+        self.assertEquals(inbox.changeToken, "a483dab3-1391-445b-b1c3-5ae9dfc81c2f#0")
 
-        self.assertEquals(outbox.resourceType, caldavxml.schedule_outbox)
-        self.assertEquals(outbox.name, None)
-        self.assertEquals(outbox.url, "/calendars/__uids__/user01/outbox/")
-        self.assertEquals(outbox.ctag, None)
-
         self.assertEqual({}, self.client.xmpp)
 
 
     def test_extractCalendarsXMPP(self):
         """
         If there is XMPP push information in a calendar home PROPFIND response,
-        L{SnowLeopard._extractCalendars} finds it and records it.
+        L{OS_X_10_6._extractCalendars} finds it and records it.
         """
         home = "/calendars/__uids__/user01/"
         self.client._extractCalendars(
-            CALENDAR_HOME_PROPFIND_RESPONSE_WITH_XMPP, home)
+            self.client._parseMultiStatus(CALENDAR_HOME_PROPFIND_RESPONSE_WITH_XMPP),
+            home
+        )
         self.assertEqual({
                 home: XMPPPush(
                     "xmpp.example.invalid:1952",
@@ -1269,13 +1256,12 @@
     def test_handleMissingXMPP(self):
         home = "/calendars/__uids__/user01/"
         self.client._extractCalendars(
-            CALENDAR_HOME_PROPFIND_RESPONSE_XMPP_MISSING, home)
+            self.client._parseMultiStatus(CALENDAR_HOME_PROPFIND_RESPONSE_XMPP_MISSING), home)
         self.assertEqual({}, self.client.xmpp)
 
-
     def test_changeEventAttendee(self):
         """
-        SnowLeopard.changeEventAttendee removes one attendee from an
+        OS_X_10_6.changeEventAttendee removes one attendee from an
         existing event and appends another.
         """
         requests = self.interceptRequests()
@@ -1299,20 +1285,17 @@
         self.assertEquals(headers.getRawHeaders('content-type'), ['text/calendar'])
 
         consumer = MemoryConsumer()
-        finished = body.startProducing(consumer)
-        def cbFinished(ignored):
-            vevent = Component.fromString(consumer.value())
-            attendees = tuple(vevent.mainComponent().properties("ATTENDEE"))
-            self.assertEquals(len(attendees), 2)
-            self.assertEquals(attendees[0].parameterValue('CN'), 'User 01')
-            self.assertEquals(attendees[1].parameterValue('CN'), 'Some Other Guy')
-        finished.addCallback(cbFinished)
-        return finished
+        yield body.startProducing(consumer)
+        vevent = Component.fromString(consumer.value())
+        attendees = tuple(vevent.mainComponent().properties("ATTENDEE"))
+        self.assertEquals(len(attendees), 2)
+        self.assertEquals(attendees[0].parameterValue('CN'), 'User 01')
+        self.assertEquals(attendees[1].parameterValue('CN'), 'Some Other Guy')
 
 
     def test_addEvent(self):
         """
-        L{SnowLeopard.addEvent} PUTs the event passed to it to the
+        L{OS_X_10_6.addEvent} PUTs the event passed to it to the
         server and updates local state to reflect its existence.
         """
         requests = self.interceptRequests()
@@ -1354,7 +1337,7 @@
     @inlineCallbacks
     def test_addInvite(self):
         """
-        L{SnowLeopard.addInvite} PUTs the event passed to it to the
+        L{OS_X_10_6.addInvite} PUTs the event passed to it to the
         server and updates local state to reflect its existence, but
         it also does attendee auto-complete and free-busy checks before
         the PUT.
@@ -1370,7 +1353,7 @@
         self.client.outbox = "/calendars/__uids__/user01/outbox/"
 
         @inlineCallbacks
-        def _testReport(*args):
+        def _testReport(*args, **kwargs):
             expectedResponseCode, method, url, headers, body = args
             self.assertEqual(expectedResponseCode, MULTI_STATUS)
             self.assertEqual(method, 'REPORT')
@@ -1406,11 +1389,11 @@
             
             returnValue(response)
         
-        def _testPost02(*args):
-            return _testPost(*args, attendee="ATTENDEE:mailto:user02 at example.com")
+        def _testPost02(*args, **kwargs):
+            return _testPost(*args, attendee="ATTENDEE:mailto:user02 at example.com", **kwargs)
         
-        def _testPost03(*args):
-            return _testPost(*args, attendee="ATTENDEE:mailto:user03 at example.com")
+        def _testPost03(*args, **kwargs):
+            return _testPost(*args, attendee="ATTENDEE:mailto:user03 at example.com", **kwargs)
         
         @inlineCallbacks
         def _testPut(*args, **kwargs):
@@ -1435,15 +1418,15 @@
         
         requests = [_testReport, _testPost02, _testReport, _testPost03, _testPut,]
         
-        def _requestHandler(*args):
+        def _requestHandler(*args, **kwargs):
             handler = requests.pop(0)
-            return handler(*args)
+            return handler(*args, **kwargs)
         self.client._request = _requestHandler
         yield self.client.addInvite('/mumble/frotz.ics', vcalendar)
 
     def test_deleteEvent(self):
         """
-        L{SnowLeopard.deleteEvent} DELETEs the event at the relative
+        L{OS_X_10_6.deleteEvent} DELETEs the event at the relative
         URL passed to it and updates local state to reflect its
         removal.
         """
@@ -1475,9 +1458,9 @@
         return d
 
 
-class UpdateCalendarTests(SnowLeopardMixin, TestCase):
+class UpdateCalendarTests(OS_X_10_6Mixin, TestCase):
     """
-    Tests for L{SnowLeopard._updateCalendar}.
+    Tests for L{OS_X_10_6._updateCalendar}.
     """
 
     _CALENDAR_PROPFIND_RESPONSE_BODY = """\
@@ -1612,12 +1595,12 @@
 
         calendar = Calendar(None, set(('VEVENT',)), 'calendar', '/something/', None)
         self.client._calendars[calendar.url] = calendar
-        self.client._updateCalendar(calendar)
+        self.client._updateCalendar(calendar, "1234")
         result, req = requests.pop(0)
         expectedResponseCode, method, url, _ignore_headers, _ignore_body = req
         self.assertEqual('PROPFIND', method)
         self.assertEqual('http://127.0.0.1/something/', url)
-        self.assertEqual(MULTI_STATUS, expectedResponseCode)
+        self.assertEqual((MULTI_STATUS,), expectedResponseCode)
 
         result.callback(
             MemoryResponse(
@@ -1628,7 +1611,7 @@
         expectedResponseCode, method, url, _ignore_headers, _ignore_body = req
         self.assertEqual('REPORT', method)
         self.assertEqual('http://127.0.0.1/something/', url)
-        self.assertEqual(MULTI_STATUS, expectedResponseCode)
+        self.assertEqual((MULTI_STATUS,), expectedResponseCode)
 
         # Someone else comes along and gets rid of the event
         del self.client._events["/something/anotherthing.ics"]
@@ -1655,12 +1638,12 @@
 
         calendar = Calendar(None, set(('VEVENT',)), 'calendar', '/something/', None)
         self.client._calendars[calendar.url] = calendar
-        self.client._updateCalendar(calendar)
+        self.client._updateCalendar(calendar, "1234")
         result, req = requests.pop(0)
         expectedResponseCode, method, url, _ignore_headers, _ignore_body = req
         self.assertEqual('PROPFIND', method)
         self.assertEqual('http://127.0.0.1/something/', url)
-        self.assertEqual(MULTI_STATUS, expectedResponseCode)
+        self.assertEqual((MULTI_STATUS,), expectedResponseCode)
 
         result.callback(
             MemoryResponse(
@@ -1671,7 +1654,7 @@
         expectedResponseCode, method, url, _ignore_headers, _ignore_body = req
         self.assertEqual('REPORT', method)
         self.assertEqual('http://127.0.0.1/something/', url)
-        self.assertEqual(MULTI_STATUS, expectedResponseCode)
+        self.assertEqual((MULTI_STATUS,), expectedResponseCode)
 
         result.callback(
             MemoryResponse(
@@ -1685,7 +1668,7 @@
         expectedResponseCode, method, url, _ignore_headers, _ignore_body = req
         self.assertEqual('REPORT', method)
         self.assertEqual('http://127.0.0.1/something/', url)
-        self.assertEqual(MULTI_STATUS, expectedResponseCode)
+        self.assertEqual((MULTI_STATUS,), expectedResponseCode)
 
         result.callback(
             MemoryResponse(
@@ -1696,13 +1679,13 @@
         self.assertTrue(self.client._events['/something/else.ics'].etag is not None)
 
 
-class VFreeBusyTests(SnowLeopardMixin, TestCase):
+class VFreeBusyTests(OS_X_10_6Mixin, TestCase):
     """
-    Tests for L{SnowLeopard.requestAvailability}.
+    Tests for L{OS_X_10_6.requestAvailability}.
     """
     def test_requestAvailability(self):
         """
-        L{SnowLeopard.requestAvailability} accepts a date range and a set of
+        L{OS_X_10_6.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.

Modified: CalendarServer/trunk/contrib/performance/loadtest/test_population.py
===================================================================
--- CalendarServer/trunk/contrib/performance/loadtest/test_population.py	2012-04-10 00:41:41 UTC (rev 9003)
+++ CalendarServer/trunk/contrib/performance/loadtest/test_population.py	2012-04-10 15:41:35 UTC (rev 9004)
@@ -1,5 +1,5 @@
 ##
-# Copyright (c) 2011 Apple Inc. All rights reserved.
+# Copyright (c) 2011-2012 Apple Inc. All rights reserved.
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
 # you may not use this file except in compliance with the License.
@@ -37,10 +37,37 @@
         for user in users:
             logger.observe(dict(
                     type='response', method='GET', success=True,
-                    duration=1.23, user=user))
+                    duration=1.23, user=user, client_type="test", client_id="1234"))
         self.assertEqual(len(users), logger.countUsers())
 
 
+    def test_countClients(self):
+        """
+        L{ReportStatistics.countClients} returns the number of clients observed to
+        have acted in the simulation.
+        """
+        logger = ReportStatistics()
+        clients = ['c01', 'c02', 'c03']
+        for client in clients:
+            logger.observe(dict(
+                    type='response', method='GET', success=True,
+                    duration=1.23, user="user01", client_type="test", client_id=client))
+        self.assertEqual(len(clients), logger.countClients())
+
+
+    def test_clientFailures(self):
+        """
+        L{ReportStatistics.countClients} returns the number of clients observed to
+        have acted in the simulation.
+        """
+        logger = ReportStatistics()
+        clients = ['c01', 'c02', 'c03']
+        for client in clients:
+            logger.observe(dict(
+                    type='client-failure', reason="testing %s" % (client,)))
+        self.assertEqual(len(clients), logger.countClientFailures())
+
+
     def test_noFailures(self):
         """
         If fewer than 1% of requests fail, fewer than 1% of requests take 5
@@ -50,7 +77,7 @@
         logger = ReportStatistics()
         logger.observe(dict(
                 type='response', method='GET', success=True,
-                duration=2.5, user='user01'))
+                duration=2.5, user='user01', client_type="test", client_id="1234"))
         self.assertEqual([], logger.failures())
 
 
@@ -60,13 +87,13 @@
         list containing a string describing this.
         """
         logger = ReportStatistics()
-        for i in range(98):
+        for _ignore in range(98):
             logger.observe(dict(
                     type='response', method='GET', success=True,
-                    duration=2.5, user='user01'))
+                    duration=2.5, user='user01', client_type="test", client_id="1234"))
         logger.observe(dict(
                 type='response', method='GET', success=False,
-                duration=2.5, user='user01'))
+                duration=2.5, user='user01', client_type="test", client_id="1234"))
         self.assertEqual(
             ["Greater than 1% GET failed"],
             logger.failures())
@@ -79,14 +106,14 @@
         describing that.
         """
         logger = ReportStatistics()
-        for i in range(94):
+        for _ignore in range(94):
             logger.observe(dict(
                     type='response', method='GET', success=True,
-                    duration=2.5, user='user01'))
-        for i in range(5):
+                    duration=2.5, user='user01', client_type="test", client_id="1234"))
+        for _ignore in range(5):
             logger.observe(dict(
                     type='response', method='GET', success=True,
-                    duration=3.5, user='user02'))
+                    duration=3.5, user='user02', client_type="test", client_id="1234"))
         self.assertEqual(
             ["Greater than 5% GET exceeded 3 second response time"],
             logger.failures())
@@ -99,13 +126,13 @@
         describing that.
         """
         logger = ReportStatistics()
-        for i in range(98):
+        for _ignore in range(98):
             logger.observe(dict(
                     type='response', method='GET', success=True,
-                    duration=2.5, user='user01'))
+                    duration=2.5, user='user01', client_type="test", client_id="1234"))
         logger.observe(dict(
                 type='response', method='GET', success=True,
-                duration=5.5, user='user01'))
+                duration=5.5, user='user01', client_type="test", client_id="1234"))
         self.assertEqual(
             ["Greater than 1% GET exceeded 5 second response time"],
             logger.failures())
@@ -116,19 +143,19 @@
         The counts for one method do not affect the results of another method.
         """
         logger = ReportStatistics()
-        for i in range(99):
+        for _ignore in range(99):
             logger.observe(dict(
                     type='response', method='GET', success=True,
-                    duration=2.5, user='user01'))
+                    duration=2.5, user='user01', client_type="test", client_id="1234"))
             logger.observe(dict(
                     type='response', method='POST', success=True,
-                    duration=2.5, user='user01'))
+                    duration=2.5, user='user01', client_type="test", client_id="1234"))
 
         logger.observe(dict(
                 type='response', method='GET', success=False,
-                duration=2.5, user='user01'))
+                duration=2.5, user='user01', client_type="test", client_id="1234"))
         logger.observe(dict(
                 type='response', method='POST', success=False,
-                duration=2.5, user='user01'))
+                duration=2.5, user='user01', client_type="test", client_id="1234"))
 
         self.assertEqual([], logger.failures())

Modified: CalendarServer/trunk/contrib/performance/loadtest/test_profiles.py
===================================================================
--- CalendarServer/trunk/contrib/performance/loadtest/test_profiles.py	2012-04-10 00:41:41 UTC (rev 9003)
+++ CalendarServer/trunk/contrib/performance/loadtest/test_profiles.py	2012-04-10 15:41:35 UTC (rev 9004)
@@ -207,6 +207,7 @@
         self.email = "mailto:user%02d at example.com" % (number,)
         self.uuid = "urn:uuid:user%02d" % (number,)
         self.rescheduled = set()
+        self.started = True
 
 
     def addEvent(self, href, vevent):

Modified: CalendarServer/trunk/contrib/performance/loadtest/test_sim.py
===================================================================
--- CalendarServer/trunk/contrib/performance/loadtest/test_sim.py	2012-04-10 00:41:41 UTC (rev 9003)
+++ CalendarServer/trunk/contrib/performance/loadtest/test_sim.py	2012-04-10 15:41:35 UTC (rev 9004)
@@ -1,5 +1,5 @@
 ##
-# Copyright (c) 2011 Apple Inc. All rights reserved.
+# Copyright (c) 2011-2012 Apple Inc. All rights reserved.
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
 # you may not use this file except in compliance with the License.
@@ -27,7 +27,7 @@
 from twistedcaldav.directory.directory import DirectoryRecord
 
 from contrib.performance.stats import NormalDistribution
-from contrib.performance.loadtest.ical import SnowLeopard
+from contrib.performance.loadtest.ical import OS_X_10_6
 from contrib.performance.loadtest.profiles import Eventer, Inviter, Accepter
 from contrib.performance.loadtest.population import (
     SmoothRampUp, ClientType, PopulationParameters, Populator, CalendarClientSimulator,
@@ -165,7 +165,7 @@
                 [ProfileType(BrokenProfile, {'runResult': profileRunResult})]))
         sim = CalendarClientSimulator(
             [self._user('alice')], Populator(None), params, None, 'http://example.com:1234/')
-        sim.add(1)
+        sim.add(1, 1)
         sim.stop()
         clientRunResult.errback(RuntimeError("Some fictional client problem"))
         profileRunResult.errback(RuntimeError("Some fictional profile problem"))
@@ -186,7 +186,7 @@
         for thunk in self._whenRunning:
             thunk()
         msg(self.message)
-        for phase, event, thunk in self._triggers:
+        for _ignore_phase, event, thunk in self._triggers:
             if event == 'shutdown':
                 thunk()
 
@@ -448,7 +448,7 @@
         config = FilePath(self.mktemp())
         config.setContent(writePlistToString({
                     "clients": [{
-                            "software": "contrib.performance.loadtest.ical.SnowLeopard",
+                            "software": "contrib.performance.loadtest.ical.OS_X_10_6",
                             "params": {"foo": "bar"},
                             "profiles": [{
                                     "params": {
@@ -466,7 +466,7 @@
         sim = LoadSimulator.fromCommandLine(['--config', config.path])
         expectedParameters = PopulationParameters()
         expectedParameters.addClient(
-            3, ClientType(SnowLeopard, {"foo": "bar"}, [ProfileType(Eventer, {
+            3, ClientType(OS_X_10_6, {"foo": "bar"}, [ProfileType(Eventer, {
                             "interval": 25,
                             "eventStartDistribution": NormalDistribution(123, 456)})]))
         self.assertEquals(sim.parameters, expectedParameters)
@@ -483,7 +483,7 @@
         sim = LoadSimulator.fromCommandLine(['--config', config.path])
         expectedParameters = PopulationParameters()
         expectedParameters.addClient(
-            1, ClientType(SnowLeopard, {}, [Eventer, Inviter, Accepter]))
+            1, ClientType(OS_X_10_6, {}, [Eventer, Inviter, Accepter]))
         self.assertEquals(sim.parameters, expectedParameters)
 
 
@@ -513,7 +513,7 @@
             None, observers, reactor=Reactor())
         io = StringIO()
         sim.run(io)
-        self.assertEquals(io.getvalue(), "PASS\n")
+        self.assertEquals(io.getvalue(), "\n*** PASS\n")
         self.assertTrue(observers[0].reported)
         self.assertEquals(
             observers[0].events[0]['message'], (Reactor.message,))

Modified: CalendarServer/trunk/contrib/performance/loadtest/test_trafficlogger.py
===================================================================
--- CalendarServer/trunk/contrib/performance/loadtest/test_trafficlogger.py	2012-04-10 00:41:41 UTC (rev 9003)
+++ CalendarServer/trunk/contrib/performance/loadtest/test_trafficlogger.py	2012-04-10 15:41:35 UTC (rev 9004)
@@ -30,7 +30,7 @@
     An interface which can be used to verify some interface-related behavior of
     L{loggedReactor}.
     """
-    def probe():
+    def probe(): #@NoSelf
         pass
 
 
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.macosforge.org/pipermail/calendarserver-changes/attachments/20120410/ab019b0f/attachment-0001.html>


More information about the calendarserver-changes mailing list