[CalendarServer-changes] [15297] CalendarServer/trunk

source_changes at macosforge.org source_changes at macosforge.org
Fri Nov 6 14:41:28 PST 2015


Revision: 15297
          http://trac.calendarserver.org//changeset/15297
Author:   sagen at apple.com
Date:     2015-11-06 14:41:28 -0800 (Fri, 06 Nov 2015)
Log Message:
-----------
Add attachments, sharing, and calendarserver_principal_search report to sim

Modified Paths:
--------------
    CalendarServer/trunk/calendarserver/push/amppush.py
    CalendarServer/trunk/calendarserver/push/test/test_amppush.py
    CalendarServer/trunk/contrib/performance/loadtest/clients.plist
    CalendarServer/trunk/contrib/performance/loadtest/config.plist
    CalendarServer/trunk/contrib/performance/loadtest/ical.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_profiles.py
    CalendarServer/trunk/contrib/performance/loadtest/test_sim.py

Added Paths:
-----------
    CalendarServer/trunk/contrib/performance/loadtest/request-data/OS_X_10_11/
    CalendarServer/trunk/contrib/performance/loadtest/request-data/OS_X_10_11/Profile
    CalendarServer/trunk/contrib/performance/loadtest/request-data/OS_X_10_11/StartupProfile
    CalendarServer/trunk/contrib/performance/loadtest/request-data/OS_X_10_11/notification_multiget_report_hrefs.request
    CalendarServer/trunk/contrib/performance/loadtest/request-data/OS_X_10_11/notification_sync.request
    CalendarServer/trunk/contrib/performance/loadtest/request-data/OS_X_10_11/poll_calendar_depth1_propfind.request
    CalendarServer/trunk/contrib/performance/loadtest/request-data/OS_X_10_11/poll_calendar_propfind.request
    CalendarServer/trunk/contrib/performance/loadtest/request-data/OS_X_10_11/poll_calendarhome_depth1_propfind.request
    CalendarServer/trunk/contrib/performance/loadtest/request-data/OS_X_10_11/poll_calendarhome_sync.request
    CalendarServer/trunk/contrib/performance/loadtest/request-data/OS_X_10_11/poll_notification_depth1_propfind.request
    CalendarServer/trunk/contrib/performance/loadtest/request-data/OS_X_10_11/post_freebusy.request
    CalendarServer/trunk/contrib/performance/loadtest/request-data/OS_X_10_11/principal_search_report.request
    CalendarServer/trunk/contrib/performance/loadtest/request-data/OS_X_10_11/report_principal_search.request
    CalendarServer/trunk/contrib/performance/loadtest/request-data/OS_X_10_11/startup_calendar_color_proppatch.request
    CalendarServer/trunk/contrib/performance/loadtest/request-data/OS_X_10_11/startup_calendar_description_proppatch.request.xml
    CalendarServer/trunk/contrib/performance/loadtest/request-data/OS_X_10_11/startup_calendar_displayname_proppatch.request
    CalendarServer/trunk/contrib/performance/loadtest/request-data/OS_X_10_11/startup_calendar_order_proppatch.request
    CalendarServer/trunk/contrib/performance/loadtest/request-data/OS_X_10_11/startup_calendar_timezone_proppatch.request
    CalendarServer/trunk/contrib/performance/loadtest/request-data/OS_X_10_11/startup_calendar_transparent_proppatch.request
    CalendarServer/trunk/contrib/performance/loadtest/request-data/OS_X_10_11/startup_calendarhome_default_alarm_date_proppatch.request
    CalendarServer/trunk/contrib/performance/loadtest/request-data/OS_X_10_11/startup_calendarhome_default_alarm_datetime_proppatch.request
    CalendarServer/trunk/contrib/performance/loadtest/request-data/OS_X_10_11/startup_create_calendar.request
    CalendarServer/trunk/contrib/performance/loadtest/request-data/OS_X_10_11/startup_delegate_principal_propfind.request
    CalendarServer/trunk/contrib/performance/loadtest/request-data/OS_X_10_11/startup_principal_expand.request
    CalendarServer/trunk/contrib/performance/loadtest/request-data/OS_X_10_11/startup_principal_initial_propfind.request
    CalendarServer/trunk/contrib/performance/loadtest/request-data/OS_X_10_11/startup_principal_propfind.request
    CalendarServer/trunk/contrib/performance/loadtest/request-data/OS_X_10_11/startup_principals_report.request
    CalendarServer/trunk/contrib/performance/loadtest/request-data/OS_X_10_11/startup_query_events_depth1_report.request
    CalendarServer/trunk/contrib/performance/loadtest/request-data/OS_X_10_11/startup_well_known_propfind.request

Modified: CalendarServer/trunk/calendarserver/push/amppush.py
===================================================================
--- CalendarServer/trunk/calendarserver/push/amppush.py	2015-11-06 22:37:43 UTC (rev 15296)
+++ CalendarServer/trunk/calendarserver/push/amppush.py	2015-11-06 22:41:28 UTC (rev 15297)
@@ -42,7 +42,7 @@
 
 
 class UnsubscribeFromID(amp.Command):
-    arguments = [('token', amp.String()), ('id', amp.String())]
+    arguments = [('id', amp.String())]
     response = [('status', amp.String())]
 
 
@@ -257,7 +257,7 @@
         return {"status" : "OK"}
     SubscribeToID.responder(subscribe)
 
-    def unsubscribe(self, token, id):
+    def unsubscribe(self, id):
         try:
             del self.subscriptions[id]
         except KeyError:

Modified: CalendarServer/trunk/calendarserver/push/test/test_amppush.py
===================================================================
--- CalendarServer/trunk/calendarserver/push/test/test_amppush.py	2015-11-06 22:37:43 UTC (rev 15296)
+++ CalendarServer/trunk/calendarserver/push/test/test_amppush.py	2015-11-06 22:41:28 UTC (rev 15297)
@@ -114,7 +114,7 @@
 
         client1.reset()
         client2.reset()
-        client2.unsubscribe("token2", "/CalDAV/localhost/user01/")
+        client2.unsubscribe("/CalDAV/localhost/user01/")
         service.enqueue(
             None, "/CalDAV/localhost/user01/",
             dataChangedTimestamp=dataChangedTimestamp,

Modified: CalendarServer/trunk/contrib/performance/loadtest/clients.plist
===================================================================
--- CalendarServer/trunk/contrib/performance/loadtest/clients.plist	2015-11-06 22:37:43 UTC (rev 15296)
+++ CalendarServer/trunk/contrib/performance/loadtest/clients.plist	2015-11-06 22:41:28 UTC (rev 15297)
@@ -31,16 +31,16 @@
 
 				<!-- Here is a OS X client simulator. -->
 				<key>software</key>
-				<string>contrib.performance.loadtest.ical.OS_X_10_7</string>
+				<string>contrib.performance.loadtest.ical.OS_X_10_11</string>
 
-				<!-- Arguments to use to initialize the OS_X_10_7 instance. -->
+				<!-- Arguments to use to initialize the OS_X_10_11 instance. -->
 				<key>params</key>
 				<dict>
 					<!-- Name that appears in logs. -->
 					<key>title</key>
-					<string>10.7</string>
-	
-					<!-- OS_X_10_7 can poll the calendar home at some interval. This is
+					<string>10.11</string>
+
+					<!-- OS_X_10_11 can poll the calendar home at some interval. This is
 						in seconds. -->
 					<key>calendarHomePollInterval</key>
 					<integer>30</integer>
@@ -51,7 +51,7 @@
 						and use it if possible. Still fall back to polling if there is no xmpp push
 						advertised. -->
 					<key>supportPush</key>
-					<false />
+					<true/>
 
 					<key>supportAmpPush</key>
 					<true/>
@@ -144,13 +144,13 @@
 										<!-- Half of all events will be non-recurring -->
 										<key>none</key>
 										<integer>50</integer>
-										
+
 										<!-- Daily and weekly are pretty common -->
 										<key>daily</key>
 										<integer>10</integer>
 										<key>weekly</key>
 										<integer>20</integer>
-										
+
 										<!-- Monthly, yearly, daily & weekly limit not so common -->
 										<key>monthly</key>
 										<integer>2</integer>
@@ -160,7 +160,7 @@
 										<integer>2</integer>
 										<key>weeklylimit</key>
 										<integer>5</integer>
-										
+
 										<!-- Work days pretty common -->
 										<key>workdays</key>
 										<integer>10</integer>
@@ -171,6 +171,10 @@
 					</dict>
 
 					<!-- This profile will create a new event, and then periodically update the ACKNOWLEDGED property. -->
+
+					<!-- Rather than use EventUpdater which always updates the same event,
+					you can use Eventer to create events and TitleChanger or Attacher to change
+					random events -->
 					<dict>
 						<key>class</key>
 						<string>contrib.performance.loadtest.profiles.EventUpdater</string>
@@ -247,13 +251,13 @@
 										<!-- Half of all events will be non-recurring -->
 										<key>none</key>
 										<integer>50</integer>
-										
+
 										<!-- Daily and weekly are pretty common -->
 										<key>daily</key>
 										<integer>25</integer>
 										<key>weekly</key>
 										<integer>25</integer>
-										
+
 										<!-- Monthly, yearly, daily & weekly limit not so common -->
 										<key>monthly</key>
 										<integer>0</integer>
@@ -263,7 +267,7 @@
 										<integer>0</integer>
 										<key>weeklylimit</key>
 										<integer>0</integer>
-										
+
 										<!-- Work days pretty common -->
 										<key>workdays</key>
 										<integer>0</integer>
@@ -273,6 +277,99 @@
 						</dict>
 					</dict>
 
+					<!-- Picks a random event and changes the title -->
+					<dict>
+						<key>class</key>
+						<string>contrib.performance.loadtest.profiles.TitleChanger</string>
+
+						<key>params</key>
+						<dict>
+							<key>enabled</key>
+							<true/>
+
+							<!-- Define the interval (in seconds) at which this profile will use
+								its client to create a new event. -->
+							<key>interval</key>
+							<integer>60</integer>
+
+						</dict>
+					</dict>
+
+					<!-- Picks a random event and attaches -->
+					<dict>
+						<key>class</key>
+						<string>contrib.performance.loadtest.profiles.Attacher</string>
+
+						<key>params</key>
+						<dict>
+							<key>enabled</key>
+							<true/>
+
+							<!-- Define the interval (in seconds) at which this profile will use
+								its client to create a new event. -->
+							<key>interval</key>
+							<integer>60</integer>
+
+							<!-- Define the attachment size distribution. -->
+							<key>fileSizeDistribution</key>
+							<dict>
+								<key>type</key>
+								<string>contrib.performance.stats.NormalDistribution</string>
+								<key>params</key>
+								<dict>
+									<!-- mu gives the mean of the normal distribution (in seconds). -->
+									<key>mu</key>
+									<integer>500000</integer>
+
+									<!-- and sigma gives its standard deviation. -->
+									<key>sigma</key>
+									<integer>100000</integer>
+								</dict>
+							</dict>
+
+						</dict>
+					</dict>
+
+					<!-- Removes events from calendars exceeding a threshold -->
+					<dict>
+						<key>class</key>
+						<string>contrib.performance.loadtest.profiles.EventCountLimiter</string>
+
+						<key>params</key>
+						<dict>
+							<key>enabled</key>
+							<true/>
+
+							<!-- Define the interval (in seconds) at which this profile will check
+								for too-large collections. -->
+							<key>interval</key>
+							<integer>60</integer>
+
+							<!-- The upper bound. -->
+							<key>eventCountLimit</key>
+							<integer>100</integer>
+
+						</dict>
+					</dict>
+
+
+					<!-- Shares calendars -->
+					<dict>
+						<key>class</key>
+						<string>contrib.performance.loadtest.profiles.CalendarSharer</string>
+
+						<key>params</key>
+						<dict>
+							<key>enabled</key>
+							<true/>
+
+							<!-- Define the interval (in seconds) at which this profile will share calendars. -->
+							<key>interval</key>
+							<integer>300</integer>
+
+						</dict>
+					</dict>
+
 					<!-- This profile invites some number of new attendees to new events. -->
 					<dict>
 						<key>class</key>
@@ -281,7 +378,7 @@
 						<key>params</key>
 						<dict>
 							<key>enabled</key>
-							<true/>
+							<false/>
 
 							<!-- Define the frequency at which new invitations will be sent out. -->
 							<key>sendInvitationDistribution</key>
@@ -292,7 +389,7 @@
 								<dict>
 									<!-- mu gives the mean of the normal distribution (in seconds). -->
 									<key>mu</key>
-									<integer>60</integer>
+									<integer>10</integer>
 
 									<!-- and sigma gives its standard deviation. -->
 									<key>sigma</key>
@@ -301,12 +398,12 @@
 							</dict>
 
 							<!-- Define the distribution of who will be invited to an event.
-							
+
 								When inviteeClumping is turned on each invitee is based on a sample of
 								users "close to" the organizer based on account index. If the clumping
 								is too "tight" for the requested number of attendees, then invites for
 								those larger numbers will simply fail (the sim will report that situation).
-								
+
 								When inviteeClumping is off invitees will be sampled across an entire
 								range of account indexes. In this case the distribution ought to be a
 								UniformIntegerDistribution with min=0 and max set to the number of accounts.
@@ -330,13 +427,13 @@
 							<true/>
 
 							<!-- Define the distribution of how many attendees will be invited to an event.
-							
+
 								LogNormal is the best fit to observed data.
 
 
 								For LogNormal "mode" is the peak, "mean" is the mean value.	For invites,
 								mode should typically be 1, and mean whatever matches the user behavior.
-								Our typical mean is 6. 							
+								Our typical mean is 6.
 							     -->
 							<key>inviteeCountDistribution</key>
 							<dict>
@@ -418,13 +515,13 @@
 										<!-- Half of all events will be non-recurring -->
 										<key>none</key>
 										<integer>50</integer>
-										
+
 										<!-- Daily and weekly are pretty common -->
 										<key>daily</key>
 										<integer>10</integer>
 										<key>weekly</key>
 										<integer>20</integer>
-										
+
 										<!-- Monthly, yearly, daily & weekly limit not so common -->
 										<key>monthly</key>
 										<integer>2</integer>
@@ -434,7 +531,7 @@
 										<integer>2</integer>
 										<key>weeklylimit</key>
 										<integer>5</integer>
-										
+
 										<!-- Work days pretty common -->
 										<key>workdays</key>
 										<integer>10</integer>
@@ -459,7 +556,7 @@
 								accepting it.
 
 								For LogNormal "mode" is the peak, "median" is the 50% cummulative value
-								(i.e., half of the user have accepted by that time).								
+								(i.e., half of the user have accepted by that time).
 							-->
 							<key>acceptDelayDistribution</key>
 							<dict>

Modified: CalendarServer/trunk/contrib/performance/loadtest/config.plist
===================================================================
--- CalendarServer/trunk/contrib/performance/loadtest/config.plist	2015-11-06 22:37:43 UTC (rev 15296)
+++ CalendarServer/trunk/contrib/performance/loadtest/config.plist	2015-11-06 22:41:28 UTC (rev 15297)
@@ -21,7 +21,7 @@
 	<dict>
 		<!-- Identify the server to be load tested. -->
 		<key>server</key>
-		<string>https://127.0.0.1:8443</string>
+		<string>https://localhost:8443</string>
 
 		<!-- The template URI for doing initial principal lookup on. -->
 		<key>principalPathTemplate</key>
@@ -96,12 +96,12 @@
 			<dict>
 				<!-- groups gives the total number of groups of clients to introduce. -->
 				<key>groups</key>
-				<integer>20</integer>
+				<integer>10</integer>
 
 				<!-- groupSize is the number of clients in each group of clients. It's
 					really only a "smooth" ramp up if this is pretty small. -->
 				<key>groupSize</key>
-				<integer>1</integer>
+				<integer>3</integer>
 
 				<!-- Number of seconds between the introduction of each group. -->
 				<key>interval</key>
@@ -118,7 +118,7 @@
 		<!-- Define some log observers to report on the load test. -->
 		<key>observers</key>
 		<array>
-			<!-- ReportStatistics generates an end-of-run summary of the HTTP requests 
+			<!-- ReportStatistics generates an end-of-run summary of the HTTP requests
 				made, their timings, and their results. -->
 			<dict>
 				<key>type</key>
@@ -128,7 +128,7 @@
 					<!-- The thresholds for each request type -->
 					<key>thresholdsPath</key>
 					<string>contrib/performance/loadtest/thresholds.json</string>
-					
+
 					<!-- The benchmarks for overall QoS -->
 					<key>benchmarksPath</key>
 					<string>contrib/performance/loadtest/benchmarks.json</string>
@@ -138,8 +138,8 @@
 					<real>1.0</real>
 				</dict>
 			</dict>
-	
-			<!-- RequestLogger generates a realtime log of all HTTP requests made 
+
+			<!-- RequestLogger generates a realtime log of all HTTP requests made
 				during the load test. -->
 			<dict>
 				<key>type</key>
@@ -148,9 +148,9 @@
 				<dict>
 				</dict>
 			</dict>
-	
-			<!-- OperationLogger generates an end-of-run summary of the gross operations 
-				performed (logical operations which may span more than one HTTP request, 
+
+			<!-- OperationLogger generates an end-of-run summary of the gross operations
+				performed (logical operations which may span more than one HTTP request,
 				such as inviting an attendee to an event). -->
 			<dict>
 				<key>type</key>
@@ -160,11 +160,11 @@
 					<!-- The thresholds for each operation type -->
 					<key>thresholdsPath</key>
 					<string>contrib/performance/loadtest/thresholds.json</string>
-					
+
 					<!-- The % of operations beyond the lag cut-off that constitute a failed test -->
 					<key>lagCutoff</key>
 					<real>1.0</real>
-					
+
 					<!-- The % of failures that constitute a failed test -->
 					<key>failCutoff</key>
 					<real>1.0</real>

Modified: CalendarServer/trunk/contrib/performance/loadtest/ical.py
===================================================================
--- CalendarServer/trunk/contrib/performance/loadtest/ical.py	2015-11-06 22:37:43 UTC (rev 15296)
+++ CalendarServer/trunk/contrib/performance/loadtest/ical.py	2015-11-06 22:41:28 UTC (rev 15297)
@@ -18,7 +18,10 @@
 
 from caldavclientlibrary.protocol.caldav.definitions import caldavxml
 from caldavclientlibrary.protocol.caldav.definitions import csxml
+from caldavclientlibrary.protocol.calendarserver.invite import AddInvitees, RemoveInvitee, InviteUser
+from caldavclientlibrary.protocol.calendarserver.notifications import InviteNotification
 from caldavclientlibrary.protocol.url import URL
+from caldavclientlibrary.protocol.utils.xmlhelpers import BetterElementTree
 from caldavclientlibrary.protocol.webdav.definitions import davxml
 from caldavclientlibrary.protocol.webdav.propfindparser import PropFindParser
 
@@ -45,22 +48,27 @@
 from twisted.python.util import FancyEqMixin
 from twisted.web.client import Agent, ContentDecoderAgent, GzipDecoder, \
     _DeprecatedToCurrentPolicyForHTTPS
-from twisted.web.http import OK, MULTI_STATUS, CREATED, NO_CONTENT, PRECONDITION_FAILED, MOVED_PERMANENTLY, \
-    FORBIDDEN, FOUND
+from twisted.web.http import (
+    OK, MULTI_STATUS, CREATED, NO_CONTENT, PRECONDITION_FAILED,
+    MOVED_PERMANENTLY, FORBIDDEN, FOUND, NOT_FOUND
+)
 from twisted.web.http_headers import Headers
 
 from twistedcaldav.ical import Component, Property
 
 from urlparse import urlparse, urlunparse, urlsplit, urljoin
 from uuid import uuid4
-from xml.etree import ElementTree
+from xml.etree.ElementTree import ElementTree, Element, SubElement, QName
 
+from StringIO import StringIO
+
 import json
 import os
 import random
 
-ElementTree.QName.__repr__ = lambda self: '<QName %r>' % (self.text,)
+QName.__repr__ = lambda self: '<QName %r>' % (self.text,)
 
+
 def loadRequestBody(clientType, label):
     return FilePath(__file__).sibling('request-data').child(clientType).child(label + '.request').getContent()
 
@@ -245,19 +253,72 @@
                 calendar.changeToken = ""
         return calendar
 
+    @staticmethod
+    def addInviteeXML(uid, summary, readwrite=True):
+        return AddInvitees(None, '/', [uid], readwrite, summary=summary).request_data.text
 
 
+    @staticmethod
+    def removeInviteeXML(uid):
+        invitee = InviteUser()
+        # Usually an InviteUser is populated through .parseFromUser, but we only care about a uid
+        invitee.user_uid = uid
+        return RemoveInvitee(None, '/', invitee).request_data.text
+
+
+
+class NotificationCollection(object):
+    def __init__(self, url, changeToken):
+        self.url = url
+        self.changeToken = changeToken
+        self.notifications = {}
+        self.name = "notification"
+
+    def serialize(self):
+        """
+        Create a dict of the data so we can serialize as JSON.
+        """
+
+        result = {}
+        for attr in ("url", "changeToken"):
+            result[attr] = getattr(self, attr)
+        result["notifications"] = sorted(self.notifications.keys())
+        return result
+
+
+    @staticmethod
+    def deserialize(data, notifications):
+        """
+        Convert dict (deserialized from JSON) into an L{Calendar}.
+        """
+
+        coll = NotificationCollection(None, None)
+        for attr in ("url", "changeToken"):
+            setattr(coll, attr, u2str(data[attr]))
+
+        for notification in data["notifications"]:
+            url = urljoin(coll.url, notification)
+            if url in notifications:
+                coll.notifications[notification] = notifications[url]
+            else:
+                # Ughh - a notification is missing - force changeToken to empty to trigger full resync
+                coll.changeToken = ""
+        return coll
+
+
+
 class BaseClient(object):
     """
     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
+    user = None                     # User account details
+    _events = None                  # Cache of events keyed by href
+    _calendars = None               # Cache of calendars keyed by href
+    _notificationCollection = None  # Cache of the notification collection
+    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):
@@ -408,6 +469,8 @@
     _POLL_NOTIFICATION_PROPFIND = None
     _POLL_NOTIFICATION_PROPFIND_D1 = None
 
+    _NOTIFICATION_SYNC_REPORT = None
+
     _USER_LIST_PRINCIPAL_PROPERTY_SEARCH = None
     _POST_AVAILABILITY = None
 
@@ -451,6 +514,8 @@
             calendarHomePollInterval = self.CALENDAR_HOME_POLL_INTERVAL
         self.calendarHomePollInterval = calendarHomePollInterval
 
+        self.calendarHomeHref = None
+
         self.supportPush = supportPush
 
         self.supportAmpPush = supportAmpPush
@@ -728,8 +793,10 @@
             depth='1',
             method_label="PROPFIND{home}",
         )
-        calendars = self._extractCalendars(result, calendarHomeSet)
-        returnValue((calendars, result,))
+        calendars, notificationCollection = self._extractCalendars(
+            result, calendarHomeSet
+        )
+        returnValue((calendars, notificationCollection, result,))
 
 
     @inlineCallbacks
@@ -775,6 +842,10 @@
         that from the response.
         """
         calendars = []
+        notificationCollection = None
+
+        changeTag = davxml.sync_token if self.supportSync else csxml.getctag
+
         for href in results:
 
             if href == calendarHome:
@@ -808,7 +879,6 @@
                             for comp in nodes[caldavxml.supported_calendar_component_set]:
                                 componentTypes.add(comp.get("name").upper())
 
-                    changeTag = davxml.sync_token if self.supportSync else csxml.getctag
                     calendars.append(Calendar(
                         nodeType.tag,
                         componentTypes,
@@ -817,9 +887,16 @@
                         textProps.get(changeTag, None),
                     ))
                     break
-        return calendars
+                elif nodeType.tag == csxml.notification:
+                    textProps = results[href].getTextProperties()
+                    notificationCollection = NotificationCollection(
+                        href,
+                        textProps.get(changeTag, None)
+                    )
 
+        return calendars, notificationCollection
 
+
     def _updateCalendar(self, calendar, newToken):
         """
         Update the local cached data for a calendar in an appropriate manner.
@@ -1048,12 +1125,132 @@
 
 
     @inlineCallbacks
+    def _updateNotifications(self, oldToken, newToken):
+
+        fullSync = not oldToken
+
+        # Get the list of notificatinon xml resources
+
+        result = yield self._report(
+            self._notificationCollection.url,
+            self._NOTIFICATION_SYNC_REPORT % {'sync-token': oldToken},
+            depth='1',
+            allowedStatus=(MULTI_STATUS, FORBIDDEN,),
+            otherTokens=True,
+            method_label="REPORT{sync}" if oldToken else "REPORT{sync-init}",
+        )
+        if result is None:
+            if not fullSync:
+                fullSync = True
+                result = yield self._report(
+                    self._notificationCollection.url,
+                    self._NOTIFICATION_SYNC_REPORT % {'sync-token': ''},
+                    depth='1',
+                    otherTokens=True,
+                    method_label="REPORT{sync}" if oldToken else "REPORT{sync-init}",
+                )
+            else:
+                raise IncorrectResponseCode((MULTI_STATUS,), None)
+
+        result, others = result
+
+        # Scan for the sharing invites
+        inviteNotifications = []
+        toDelete = []
+        for responseHref in result:
+            if responseHref == self._notificationCollection.url:
+                continue
+
+            # try:
+            #     etag = result[responseHref].getTextProperties()[davxml.getetag]
+            # except KeyError:
+            #     # XXX Ignore things with no etag?  Seems to be dropbox.
+            #     continue
+
+            toDelete.append(responseHref)
+
+            if result[responseHref].getStatus() / 100 == 2:
+                # Get the notification
+                response = yield self._request(
+                    OK,
+                    'GET',
+                    self.root + responseHref.encode('utf-8'),
+                    method_label="GET{notification}",
+                )
+                body = yield readBody(response)
+                node = ElementTree(file=StringIO(body)).getroot()
+                if node.tag == str(csxml.notification):
+                    nurl = URL(url=responseHref)
+                    for child in node.getchildren():
+                        if child.tag == str(csxml.invite_notification):
+                            if child.find(str(csxml.invite_noresponse)) is not None:
+                                inviteNotifications.append(
+                                    InviteNotification().parseFromNotification(
+                                        nurl, child
+                                    )
+                                )
+
+        # Accept the invites
+        for notification in inviteNotifications:
+            # Create an invite-reply
+            """
+            <?xml version="1.0" encoding="UTF-8"?>
+            <C:invite-reply xmlns:C="http://calendarserver.org/ns/">
+              <A:href xmlns:A="DAV:">urn:x-uid:10000000-0000-0000-0000-000000000002</A:href>
+              <C:invite-accepted/>
+              <C:hosturl>
+                <A:href xmlns:A="DAV:">/calendars/__uids__/10000000-0000-0000-0000-000000000001/A1DDC58B-651E-4B1C-872A-C6588CA09ADB</A:href>
+              </C:hosturl>
+              <C:in-reply-to>d2683fa9-7a50-4390-82bb-cbcea5e0fa86</C:in-reply-to>
+              <C:summary>to share</C:summary>
+            </C:invite-reply>
+            """
+            reply = Element(csxml.invite_reply)
+            href = SubElement(reply, davxml.href)
+            href.text = notification.user_uid
+            SubElement(reply, csxml.invite_accepted)
+            hosturl = SubElement(reply, csxml.hosturl)
+            href = SubElement(hosturl, davxml.href)
+            href.text = notification.hosturl
+            inReplyTo = SubElement(reply, csxml.in_reply_to)
+            inReplyTo.text = notification.uid
+            summary = SubElement(reply, csxml.summary)
+            summary.text = notification.summary
+
+            xmldoc = BetterElementTree(reply)
+            os = StringIO()
+            xmldoc.writeUTF8(os)
+            # Post to my calendar home
+            response = yield self.postXML(
+                self.calendarHomeHref,
+                os.getvalue(),
+                "POST{invite-accept}"
+            )
+
+        # Delete all the notification resources
+        for responseHref in toDelete:
+            response = yield self._request(
+                (NO_CONTENT, NOT_FOUND),
+                'DELETE',
+                self.root + responseHref.encode('utf-8'),
+                method_label="DELETE{invite}",
+            )
+
+        self._notificationCollection.changeToken = newToken
+
+
+
+    @inlineCallbacks
     def _poll(self, calendarHomeSet, firstTime):
+        """
+        This gets called during a normal poll or in response to a push
+        """
+
         if calendarHomeSet in self._checking:
             returnValue(False)
         self._checking.add(calendarHomeSet)
 
-        calendars, results = yield self._calendarHomePropfind(calendarHomeSet)
+        calendars, notificationCollection, results = yield self._calendarHomePropfind(calendarHomeSet)
 
         # First time operations
         if firstTime:
@@ -1071,12 +1268,24 @@
                 # Calendar changed - reload it
                 yield self._updateCalendar(self._calendars[cal.url], newToken)
 
-        # 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)
+        if notificationCollection is not None:
+            if self._notificationCollection:
+                oldToken = self._notificationCollection.changeToken
+            else:
+                oldToken = ""
+            self._notificationCollection = notificationCollection
+            newToken = notificationCollection.changeToken
+            yield self._updateNotifications(oldToken, newToken)
 
+        # FIXME: isn't sync report the new norm, and therefore we can remove
+        # the following?
+
+        # # 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()
@@ -1263,6 +1472,8 @@
             calendarHome = hrefs[caldavxml.calendar_home_set].toString()
             if calendarHome is None:
                 raise MissingCalendarHome
+            else:
+                self.calendarHomeHref = calendarHome
             yield self._checkCalendarsForEvents(calendarHome, firstTime=True)
             returnValue(calendarHome)
         calendarHome = yield self._newOperation("startup: %s" % (self.title,), startup())
@@ -1327,8 +1538,8 @@
             "principalURL": self.principalURL,
             "calendars": [calendar.serialize() for calendar in sorted(self._calendars.values(), key=lambda x:x.name)],
             "events": [event.serialize() for event in sorted(self._events.values(), key=lambda x:x.url)],
+            "notificationCollection" : self._notificationCollection.serialize(),
         }
-
         # Write JSON data
         with open(os.path.join(path, "index.json"), "w") as f:
             json.dump(data, f, indent=2)
@@ -1452,18 +1663,11 @@
             elif attendee.hasParameter('EMAIL'):
                 email = attendee.parameterValue('EMAIL').encode("utf-8")
 
-            # First try to discover some names to supply to the
-            # auto-completion
+            search = "<C:search-token>{}</C:search-token>".format(prefix)
+            body = self._CALENDARSERVER_PRINCIPAL_SEARCH_REPORT.format(
+                context="attendee", searchTokens=search)
             yield self._report(
-                self.principalCollection,
-                self._USER_LIST_PRINCIPAL_PROPERTY_SEARCH % {
-                    'displayname': prefix,
-                    'email': prefix,
-                    'firstname': prefix,
-                    'lastname': prefix,
-                },
-                depth=None,
-                method_label="REPORT{psearch}",
+                '/principals/', body, depth=None, method_label="REPORT{cpsearch}"
             )
 
             # Now learn about the attendee's availability
@@ -1475,6 +1679,7 @@
             )
 
 
+
     @inlineCallbacks
     def changeEventAttendee(self, href, oldAttendee, newAttendee):
         event = self._events[href]
@@ -1522,7 +1727,7 @@
         self._removeEvent(href)
 
         response = yield self._request(
-            NO_CONTENT,
+            (NO_CONTENT, NOT_FOUND),
             'DELETE',
             self.root + href.encode('utf-8'),
             method_label="DELETE{event}",
@@ -1702,7 +1907,42 @@
         returnValue(body)
 
 
+    @inlineCallbacks
+    def postAttachment(self, href, content):
+        url = self.root + "{0}?{1}".format(href, "action=attachment-add")
+        filename = 'file-{}.txt'.format(len(content))
+        headers = Headers({
+            'Content-Disposition': ['attachment; filename="{}"'.format(filename)]
+        })
+        response = yield self._request(
+            CREATED,
+            'POST',
+            url,
+            headers=headers,
+            body=StringProducer(content),
+            method_label="POST{attach}"
+        )
+        body = yield readBody(response)
+        returnValue(body)
 
+
+    @inlineCallbacks
+    def postXML(self, href, content, label):
+        headers = Headers({
+            'content-type': ['text/xml']
+        })
+        response = yield self._request(
+            (OK, CREATED, MULTI_STATUS),
+            'POST',
+            self.root + href,
+            headers=headers,
+            body=StringProducer(content),
+            method_label=label
+        )
+        body = yield readBody(response)
+        returnValue(body)
+
+
 class OS_X_10_6(BaseAppleClient):
     """
     Implementation of the OS X 10.6 iCal network behavior.
@@ -1868,7 +2108,99 @@
         returnValue(principal)
 
 
+class OS_X_10_11(BaseAppleClient):
+    """
+    Implementation of the OS X 10.11 Calendar.app network behavior.
+    """
 
+    _client_type = "OS X 10.11"
+
+    USER_AGENT = "Mac+OS+X/10.11 (15A283) CalendarAgent/361"
+
+    # The default interval, used if none is specified in external
+    # configuration.  This is also the actual value used by El
+    # Capital Calendar.app.
+    CALENDAR_HOME_POLL_INTERVAL = 15 * 60  # in seconds
+
+    # 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 = True
+
+    # Override and turn off if client does not support attendee lookups
+    _ATTENDEE_LOOKUPS = True
+
+    # Request body data
+    _LOAD_PATH = "OS_X_10_11"
+
+    _STARTUP_WELL_KNOWN = loadRequestBody(_LOAD_PATH, 'startup_well_known_propfind')
+    _STARTUP_PRINCIPAL_PROPFIND_INITIAL = loadRequestBody(_LOAD_PATH, 'startup_principal_initial_propfind')
+    _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_CREATE_CALENDAR = loadRequestBody(_LOAD_PATH, 'startup_create_calendar')
+    _STARTUP_PROPPATCH_CALENDAR_COLOR = loadRequestBody(_LOAD_PATH, 'startup_calendar_color_proppatch')
+    # _STARTUP_PROPPATCH_CALENDAR_NAME = loadRequestBody(_LOAD_PATH, 'startup_calendar_displayname_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_depth1_propfind')
+    _POLL_CALENDAR_PROPFIND = loadRequestBody(_LOAD_PATH, 'poll_calendar_propfind')
+    _POLL_CALENDAR_PROPFIND_D1 = loadRequestBody(_LOAD_PATH, 'poll_calendar_depth1_propfind')
+    _POLL_CALENDAR_MULTIGET_REPORT = loadRequestBody('OS_X_10_7', 'poll_calendar_multiget')
+    _POLL_CALENDAR_MULTIGET_REPORT_HREF = loadRequestBody('OS_X_10_7', 'poll_calendar_multiget_hrefs')
+    _POLL_CALENDAR_SYNC_REPORT = loadRequestBody('OS_X_10_7', 'poll_calendar_sync')
+    _POLL_NOTIFICATION_PROPFIND = loadRequestBody(_LOAD_PATH, 'poll_calendar_propfind')
+    _POLL_NOTIFICATION_PROPFIND_D1 = loadRequestBody(_LOAD_PATH, 'poll_notification_depth1_propfind')
+
+    _NOTIFICATION_SYNC_REPORT = loadRequestBody(_LOAD_PATH, 'notification_sync')
+
+    _USER_LIST_PRINCIPAL_PROPERTY_SEARCH = loadRequestBody('OS_X_10_7', 'user_list_principal_property_search')
+    _POST_AVAILABILITY = loadRequestBody('OS_X_10_7', 'post_availability')
+
+    _CALENDARSERVER_PRINCIPAL_SEARCH_REPORT = loadRequestBody(_LOAD_PATH, 'principal_search_report')
+
+
+    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_11, 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):
+        # Try to read data from disk - if it succeeds self.principalURL will be set
+        self.deserialize()
+
+        if self.principalURL is None:
+            # PROPFIND well-known with redirect
+            response = yield self._startupPropfindWellKnown()
+            hrefs = response.getHrefProperties()
+            if davxml.current_user_principal in hrefs:
+                self.principalURL = hrefs[davxml.current_user_principal].toString()
+            elif davxml.principal_URL in hrefs:
+                self.principalURL = hrefs[davxml.principal_URL].toString()
+            else:
+                # PROPFIND principal path to retrieve actual principal-URL
+                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._extractPrincipalDetails()
+        returnValue(principal)
+
+
 class iOS_5(BaseAppleClient):
     """
     Implementation of the iOS 5 network behavior.

Modified: CalendarServer/trunk/contrib/performance/loadtest/population.py
===================================================================
--- CalendarServer/trunk/contrib/performance/loadtest/population.py	2015-11-06 22:37:43 UTC (rev 15296)
+++ CalendarServer/trunk/contrib/performance/loadtest/population.py	2015-11-06 22:41:28 UTC (rev 15297)
@@ -162,10 +162,11 @@
 
 
 class CalendarClientSimulator(object):
-    def __init__(self, records, populator, parameters, reactor, server,
+    def __init__(self, records, populator, random, parameters, reactor, server,
                  principalPathTemplate, serializationPath, workerIndex=0, workerCount=1):
         self._records = records
         self.populator = populator
+        self._random = random
         self.reactor = reactor
         self.server = server
         self.principalPathTemplate = principalPathTemplate
@@ -184,6 +185,31 @@
         return self._records[index]
 
 
+    def getRandomUserRecord(self, besides=None):
+        count = len(self._records)
+
+        if count == 0:
+            # No records!
+            return None
+
+        if count == 1 and besides == 0:
+            # There is only one item and caller doesn't want it!
+            return None
+
+        for i in xrange(100):
+            # Try to find one that is not "besides"
+            n = self._random.randint(0, count - 1)
+            if besides != n:
+                # Got it.
+                break
+        else:
+            # Give up
+            return None
+        print("SELECTION, besides=", besides, "count=", count, "n=", n)
+        return self._records[n]
+
+
+
     def _nextUserNumber(self):
         result = self._user
         self._user += 1
@@ -640,7 +666,7 @@
     parameters.addClient(
         1, ClientType(OS_X_10_6, [Eventer, Inviter, Accepter]))
     simulator = CalendarClientSimulator(
-        populator, parameters, reactor, '127.0.0.1', 8008)
+        populator, r, parameters, reactor, '127.0.0.1', 8008)
 
     arrivalPolicy = SmoothRampUp(groups=10, groupSize=1, interval=3)
     arrivalPolicy.run(reactor, simulator)

Modified: CalendarServer/trunk/contrib/performance/loadtest/profiles.py
===================================================================
--- CalendarServer/trunk/contrib/performance/loadtest/profiles.py	2015-11-06 22:37:43 UTC (rev 15296)
+++ CalendarServer/trunk/contrib/performance/loadtest/profiles.py	2015-11-06 22:41:28 UTC (rev 15297)
@@ -31,7 +31,7 @@
 from twisted.python import context
 from twisted.python.log import msg
 from twisted.python.failure import Failure
-from twisted.internet.defer import Deferred, succeed, fail
+from twisted.internet.defer import Deferred, succeed, fail, inlineCallbacks, returnValue
 from twisted.internet.task import LoopingCall
 from twisted.web.http import PRECONDITION_FAILED
 
@@ -40,7 +40,7 @@
 from contrib.performance.stats import NearFutureDistribution, NormalDistribution, UniformDiscreteDistribution, mean, median
 from contrib.performance.stats import LogNormalDistribution, RecurrenceDistribution
 from contrib.performance.loadtest.logger import SummarizingMixin
-from contrib.performance.loadtest.ical import IncorrectResponseCode
+from contrib.performance.loadtest.ical import Calendar, IncorrectResponseCode
 
 from pycalendar.datetime import DateTime
 from pycalendar.duration import Duration
@@ -78,9 +78,46 @@
             cal
             for cal
             in self._client._calendars.itervalues()
-            if cal.resourceType == calendarType and componentType in cal.componentTypes]
+            if cal.resourceType == calendarType and componentType in cal.componentTypes
+        ]
 
 
+    def _getRandomCalendarOfType(self, componentType):
+        """
+        Return a random L{Calendar} object from the current user
+        or C{None} if there are no calendars to work with
+        """
+        calendars = self._calendarsOfType(caldavxml.calendar, componentType)
+        if not calendars:
+            return None
+        # Choose a random calendar
+        calendar = self.random.choice(calendars)
+        return calendar
+
+
+    def _getRandomEventOfType(self, componentType):
+        """
+        Return a random L{Event} object from the current user
+        or C{None} if there are no events to work with
+        """
+        calendars = self._calendarsOfType(caldavxml.calendar, componentType)
+        while calendars:
+            calendar = self.random.choice(calendars)
+            calendars.remove(calendar)
+            if not calendar.events:
+                continue
+
+            events = calendar.events.keys()
+            while events:
+                href = self.random.choice(events)
+                events.remove(href)
+                event = calendar.events[href]
+                if not event.component:
+                    continue
+                return event
+        return None
+
+
     def _isSelfAttendee(self, attendee):
         """
         Try to match one of the attendee's identifiers against one of
@@ -645,38 +682,195 @@
 
 
     def _addEvent(self):
+        # Don't perform any operations until the client is up and running
         if not self._client.started:
             return succeed(None)
 
-        calendars = self._calendarsOfType(caldavxml.calendar, "VEVENT")
+        calendar = self._getRandomCalendarOfType('VEVENT')
 
-        while calendars:
-            calendar = self.random.choice(calendars)
-            calendars.remove(calendar)
+        if not calendar:
+            # No VEVENT calendars, so no new event...
+            return succeed(None)
 
-            # Copy the template event and fill in some of its fields
-            # to make a new event to create on the calendar.
-            vcalendar = self._eventTemplate.duplicate()
-            vevent = vcalendar.mainComponent()
-            uid = str(uuid4())
-            dtstart = self._eventStartDistribution.sample()
-            dtend = dtstart + Duration(seconds=self._eventDurationDistribution.sample())
-            vevent.replaceProperty(Property("CREATED", DateTime.getNowUTC()))
-            vevent.replaceProperty(Property("DTSTAMP", DateTime.getNowUTC()))
-            vevent.replaceProperty(Property("DTSTART", dtstart))
-            vevent.replaceProperty(Property("DTEND", dtend))
-            vevent.replaceProperty(Property("UID", uid))
+        # Copy the template event and fill in some of its fields
+        # to make a new event to create on the calendar.
+        vcalendar = self._eventTemplate.duplicate()
+        vevent = vcalendar.mainComponent()
+        uid = str(uuid4())
+        dtstart = self._eventStartDistribution.sample()
+        dtend = dtstart + Duration(seconds=self._eventDurationDistribution.sample())
+        vevent.replaceProperty(Property("CREATED", DateTime.getNowUTC()))
+        vevent.replaceProperty(Property("DTSTAMP", DateTime.getNowUTC()))
+        vevent.replaceProperty(Property("DTSTART", dtstart))
+        vevent.replaceProperty(Property("DTEND", dtend))
+        vevent.replaceProperty(Property("UID", uid))
 
-            rrule = self._recurrenceDistribution.sample()
-            if rrule is not None:
-                vevent.addProperty(Property(None, None, None, pycalendar=rrule))
+        rrule = self._recurrenceDistribution.sample()
+        if rrule is not None:
+            vevent.addProperty(Property(None, None, None, pycalendar=rrule))
 
-            href = '%s%s.ics' % (calendar.url, uid)
-            d = self._client.addEvent(href, vcalendar)
-            return self._newOperation("create", d)
+        href = '%s%s.ics' % (calendar.url, uid)
+        d = self._client.addEvent(href, vcalendar)
+        return self._newOperation("create", d)
 
 
+class EventUpdaterBase(ProfileBase):
 
+    @inlineCallbacks
+    def action(self):
+        # Don't perform any operations until the client is up and running
+        if not self._client.started:
+            returnValue(None)
+
+        event = self._getRandomEventOfType('VEVENT')
+        if not event:
+            returnValue(None)
+        component = event.component
+        vevent = component.mainComponent()
+
+        label = yield self.modifyEvent(event.url, vevent)
+        vevent.replaceProperty(Property("DTSTAMP", DateTime.getNowUTC()))
+
+        event.component = component
+        yield self._newOperation(
+            label,
+            self._client.changeEvent(event.url)
+        )
+
+
+    def run(self):
+        self._call = LoopingCall(self.action)
+        self._call.clock = self._reactor
+        return self._call.start(self._interval)
+
+
+    def modifyEvent(self, href, vevent):
+        """Overriden by subclasses"""
+        pass
+
+
+class TitleChanger(EventUpdaterBase):
+
+    def setParameters(
+        self,
+        enabled=True,
+        interval=60,
+        titleLengthDistribution=NormalDistribution(10, 2)
+    ):
+        self.enabled = enabled
+        self._interval = interval
+        self._titleLength = titleLengthDistribution
+
+    def modifyEvent(self, _ignore_href, vevent):
+        length = max(5, int(self._titleLength.sample()))
+        vevent.replaceProperty(Property("SUMMARY", "Event" + "." * (length - 5)))
+        return succeed("update{title}")
+
+
+class Attacher(EventUpdaterBase):
+
+    def setParameters(
+        self,
+        enabled=True,
+        interval=60,
+        fileSizeDistribution=NormalDistribution(1024, 1),
+    ):
+        self.enabled = enabled
+        self._interval = interval
+        self._fileSize = fileSizeDistribution
+
+    @inlineCallbacks
+    def modifyEvent(self, href, vevent):
+        fileSize = int(self._fileSize.sample())
+        yield self._client.postAttachment(href, 'x' * fileSize)
+        returnValue("attach{files}")
+
+
+class EventCountLimiter(EventUpdaterBase):
+    """
+    Examines the number of events in each calendar collection, and when that
+    count exceeds eventCountLimit, events are randomly removed until the count
+    falls back to the limit.
+    """
+
+    def setParameters(
+        self,
+        enabled=True,
+        interval=60,
+        eventCountLimit=1000
+    ):
+        self.enabled = enabled
+        self._interval = interval
+        self._limit = eventCountLimit
+
+    @inlineCallbacks
+    def action(self):
+        # Don't perform any operations until the client is up and running
+        if not self._client.started:
+            returnValue(None)
+
+        for calendar in self._calendarsOfType(caldavxml.calendar, "VEVENT"):
+            while len(calendar.events) > self._limit:
+                event = calendar.events[self.random.choice(calendar.events.keys())]
+                yield self._client.deleteEvent(event.url)
+
+
+
+class CalendarSharer(ProfileBase):
+    """
+    A Calendar user who shares calendars to other random users.
+    """
+    def setParameters(
+        self,
+        enabled=True,
+        interval=60
+    ):
+        self.enabled = enabled
+        self._interval = interval
+
+
+    def run(self):
+        self._call = LoopingCall(self.action)
+        self._call.clock = self._reactor
+        return self._call.start(self._interval)
+
+
+    @inlineCallbacks
+    def action(self):
+        # Don't perform any operations until the client is up and running
+        if not self._client.started:
+            returnValue(None)
+
+        yield self.shareCalendar()
+
+
+    @inlineCallbacks
+    def shareCalendar(self):
+
+        # pick a calendar
+        calendar = self._getRandomCalendarOfType('VEVENT')
+        if not calendar:
+            returnValue(None)
+
+        # pick a random sharee
+        shareeRecord = self._sim.getRandomUserRecord(besides=self._number)
+        if shareeRecord is None:
+            returnValue(None)
+
+        # POST the sharing invite
+        mailto = "mailto:{}".format(shareeRecord.email)
+        body = Calendar.addInviteeXML(mailto, calendar.name, readwrite=True)
+        yield self._client.postXML(
+            calendar.url,
+            body,
+            label="POST{share-calendar}"
+        )
+
+
+
+# Is the purpose of this profile "EventUpdater" simply to keep updating the same
+# resource over and over?
+
 class EventUpdater(ProfileBase):
     """
     A Calendar user who creates a new event, and then updates its alarm.
@@ -741,6 +935,7 @@
 
 
     def _initEvent(self):
+        # Don't perform any operations until the client is up and running
         if not self._client.started:
             return succeed(None)
 
@@ -840,6 +1035,7 @@
 
 
     def _addTask(self):
+        # Don't perform any operations until the client is up and running
         if not self._client.started:
             return succeed(None)
 

Added: CalendarServer/trunk/contrib/performance/loadtest/request-data/OS_X_10_11/Profile
===================================================================
--- CalendarServer/trunk/contrib/performance/loadtest/request-data/OS_X_10_11/Profile	                        (rev 0)
+++ CalendarServer/trunk/contrib/performance/loadtest/request-data/OS_X_10_11/Profile	2015-11-06 22:41:28 UTC (rev 15297)
@@ -0,0 +1,267 @@
+PROPFIND ./well-known/caldav				->	/principals/
+	current-user-principal
+	principal-URL
+	resourcetype
+
+PROPFIND /principals/						->
+	current-user-principal						/principals/__uids__/<uid>
+	principal-URL								----
+	resourcetype								collection
+
+OPTIONS /principals/__uids__/<uid>/
+
+PROPFIND /principals/__uids__/<uid>/
+	calendar-home-set							/calendars/__uids__/<uid>/
+	calendar-user-address-set					mailto:user#@example.com
+												urn:uuid:<uid>
+												urn:x-uid:<uid>
+	current-user-principal						/principals/__uids__/<uid>/
+	displayname									User #
+	dropbox-home-URL							/calendars/__uids__/<uid>/dropbox/
+	email-address-set							user#@example.com
+	notification-URL							/calendars/__uids__/<uid>/notification/
+	principal-collection-set					/principals/
+	principal-URL								/principals/__uids__/<uid>/
+	resource-id									urn:x-uid:<uid>
+	schedule-inbox-URL							/calendars/__uids__/<uid>/inbox/
+	schedule-outbox-URL							/calendars/__uids__/<uid>/outbox/
+	supported-report-set						acl-principal-prop-set
+												principal-match
+												principal-property-search
+												expand-property
+												calendarserver-principal-search
+
+OPTIONS /principals/__uids__/<uid>
+
+REPORT /principals/							-> 
+	principal-search-property-set				displayname
+												email-address-set
+												calendar-user-address-set
+												calendar-user-type 
+
+PROPFIND /calendars/__uids__/<uid>/inbox/	->	
+	calendar-availability						???
+
+PROPFIND /calendars/__uids__/<uid>/
+Depth 1
+	add-member									
+	allowed-sharing-modes									
+	autoprovisioned									
+	bulk-requests									
+	calendar-alarm									
+	calendar-color									
+	calendar-description									
+	calendar-free-busy-set									
+	calendar-order									
+	calendar-timezone									
+	current-user-privilege-set					all/read/read-free-busy/write/write-properties/write-content/bind/unbind/unlock/read-acl/write-acl/read-current-user-privilege-set				
+	default-alarm-vevent-date									
+	default-alarm-vevent-datetime									
+	displayname									User #
+	getctag									
+	invite									
+	language-code									
+	location-code									
+	owner										/principals/__uids__/<uid>/
+	pre-publish-url									
+	publish-url									
+	push-transports									
+	pushkey										/CalDAV/localhost/<uid>/
+	quota-available-bytes						104857600
+	quota-used-bytes							0
+	refreshrate									
+	resource-id									
+	resourcetype								collection	
+	schedule-calendar-transp									
+	schedule-default-calendar-URL									
+	source									
+	subscribed-strip-alarms									
+	subscribed-strip-attachments									
+	subscribed-strip-todos									
+	supported-calendar-component-set			VEVENT/VTODO						
+	supported-calendar-component-sets									
+	supported-report-set						acl-principal-prop-set/principal-match/principal-property-search/expand-property/calendarserver-principal-search/calendar-query/calendar-multiget/free-busy-query/addressbook-query/addressbook-multiget/sync-collection			
+	sync-token									data:,36_58/<hex>
+ 	** and more **
+
+PROPPATCH /calendars/__uids__/<uid>/		->		default-alarm-vevent-date
+PROPPATCH /calendars/__uids__/<uid>/		->		default-alarm-vevent-datetime
+
+PROPPATCH /calendars/__uids__/<uid>/calendar/	->		calendar-order
+PROPPATCH /calendars/__uids__/<uid>/calendar/	->		displayname
+PROPPATCH /calendars/__uids__/<uid>/calendar/	->		calendar-color
+PROPPATCH /calendars/__uids__/<uid>/calendar/	->		calendar-order
+PROPPATCH /calendars/__uids__/<uid>/calendar/	->		calendar-timezone
+
+PROPPATCH /calendars/__uids__/<uid>/tasks/	->		calendar-order
+PROPPATCH /calendars/__uids__/<uid>/tasks/	->		displayname
+PROPPATCH /calendars/__uids__/<uid>/tasks/	->		calendar-color
+PROPPATCH /calendars/__uids__/<uid>/tasks/	->		calendar-order
+PROPPATCH /calendars/__uids__/<uid>/tasks/	->		calendar-timezone
+
+PROPFIND /calendars/__uids__/<uid>/calendar/->
+	getctag										37_63
+	sync-token									data:,37_63/<hex>
+
+REPORT /calendars/__uids__/<uid>/calendar/ 	->
+	getcontenttype
+	getetag
+REPORT /calendar/__uids__/<uid>/calendar/
+	getcontenttype
+	getetag
+
+PROPFIND /calendars/__uids__/<uid>/			->
+	checksum-versions							???
+
+PROPFIND /calendars/__uids__/<uid>/calendar/	->
+	getctag										
+	sync-token										
+PROPFIND /calendars/__uids__/<uid>/calendar/
+	getcontenttype								httpd/unix-directory
+	getetag										"<hex>"
+
+PROPFIND /calendars/__uids__/<uid>/			-> (again?) 
+	checksum-versions
+
+PROPFIND /calendars/__uids__/<uid>/tasks/	->
+	getctag
+	sync-token
+PROPFIND /calendars/__uids__/<uid>/tasks/	->
+	getcontenttype
+	getetag
+
+PROPFIND /calendars/__uids__/<uid>/inbox/	->
+	getctag
+	sync-token
+PROPFIND /calendars/__uids__/<uid>/inbox/	->
+	getcontenttype
+	getetag
+
+PROPFIND /calendars/__uids__/<uid>/tasks/	->
+	getctag
+	sync-token
+PROPFIND /calendars/__uids__/<uid>/tasks/	->
+	getcontenttype
+	getetag
+
+PROPFIND /calendars/__uids__/<uid>/notification/	->
+	getctag
+	sync-token
+PROPFIND /calendars/__uids__/<uid>/notification/	->
+	notificationtype
+	getetag
+
+REPORT /principals/__uids__/<uid>/
+	calendar-proxy-write-for
+		calendar-user-address-set
+		email-address-set
+		displayname
+	calendar-proxy-read-for
+		calendar-user-address-set
+		email-address-set
+		displayname
+
+REPORT /calendars/__uids__/<uid>/
+	sync-collection
+		sync-token
+		sync-level
+		*lots of properties*
+
+PROPFIND /calendars/__uids__/<uid>/inbox/
+	getctag
+	sync-token
+
+PROPFIND /principals/__uids__/<uid>/
+	calendar-proxy-write-for
+		calendar-user-address-set
+		email-address-set
+		displayname
+	calendar-proxy-read-for
+		calendar-user-address-set
+		email-address-set
+		displayname
+
+----------------------------------------------------------------
+Deep Refresh (CMD + SHIFT + R)
+
+PROPFIND /principals/__uids__/<uid>/
+	<B:calendar-home-set xmlns:B="urn:ietf:params:xml:ns:caldav"/>
+    <B:calendar-user-address-set xmlns:B="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/>
+    <B:schedule-inbox-URL xmlns:B="urn:ietf:params:xml:ns:caldav"/>
+    <B:schedule-outbox-URL xmlns:B="urn:ietf:params:xml:ns:caldav"/>
+    <A:supported-report-set/>
+
+OPTIONS /principals/__uids__/10000000-0000-0000-0000-000000000001/
+
+REPORT /principals/
+	principal-search-property-set
+
+PROPFIND /calendars/__uids__/10000000-0000-0000-0000-000000000001/inbox/
+	calendar-availability
+
+PROPFIND /calendars/__uids__/10000000-0000-0000-0000-000000000001/
+Depth 1
+	<A:add-member/>
+    <C:allowed-sharing-modes xmlns:C="http://calendarserver.org/ns/"/>
+    <D:autoprovisioned xmlns:D="http://apple.com/ns/ical/"/>
+    <E:bulk-requests xmlns:E="http://me.com/_namespace/"/>
+    <B:calendar-alarm xmlns:B="urn:ietf:params:xml:ns:caldav"/>
+    <D:calendar-color xmlns:D="http://apple.com/ns/ical/"/>
+    <B:calendar-description xmlns:B="urn:ietf:params:xml:ns:caldav"/>
+    <B:calendar-free-busy-set xmlns:B="urn:ietf:params:xml:ns:caldav"/>
+    <D:calendar-order xmlns:D="http://apple.com/ns/ical/"/>
+    <B:calendar-timezone xmlns:B="urn:ietf:params:xml:ns:caldav"/>
+    <A:current-user-privilege-set/>
+    <B:default-alarm-vevent-date xmlns:B="urn:ietf:params:xml:ns:caldav"/>
+    <B:default-alarm-vevent-datetime xmlns:B="urn:ietf:params:xml:ns:caldav"/>
+    <A:displayname/>
+    <C:getctag xmlns:C="http://calendarserver.org/ns/"/>
+    <C:invite xmlns:C="http://calendarserver.org/ns/"/>
+    <D:language-code xmlns:D="http://apple.com/ns/ical/"/>
+    <D:location-code xmlns:D="http://apple.com/ns/ical/"/>
+    <A:owner/>
+    <C:pre-publish-url xmlns:C="http://calendarserver.org/ns/"/>
+    <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/>
+    <D:refreshrate xmlns:D="http://apple.com/ns/ical/"/>
+    <A:resource-id/>
+    <A:resourcetype/>
+    <B:schedule-calendar-transp xmlns:B="urn:ietf:params:xml:ns:caldav"/>
+    <B:schedule-default-calendar-URL xmlns:B="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/"/>
+    <B:supported-calendar-component-set xmlns:B="urn:ietf:params:xml:ns:caldav"/>
+    <B:supported-calendar-component-sets xmlns:B="urn:ietf:params:xml:ns:caldav"/>
+    <A:supported-report-set/>
+    <A:sync-token/>
+
+PROPFIND on calendar/tasks/inbox/notifications as before
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+									
\ No newline at end of file

Added: CalendarServer/trunk/contrib/performance/loadtest/request-data/OS_X_10_11/StartupProfile
===================================================================
--- CalendarServer/trunk/contrib/performance/loadtest/request-data/OS_X_10_11/StartupProfile	                        (rev 0)
+++ CalendarServer/trunk/contrib/performance/loadtest/request-data/OS_X_10_11/StartupProfile	2015-11-06 22:41:28 UTC (rev 15297)
@@ -0,0 +1,71 @@
+PROPFIND  ./well-known/caldav					- startup_well_known_propfind
+
+PROPFIND  /principals/							- startup_principal_initial_propfind
+
+PROPFIND  /principals/__uids__/<uid>/			- startup_principal_propfind
+
+REPORT    /principals/							- startup_principals_report
+
+PROPFIND  /calendars/__uids__/<uid>/inbox/		- ???
+	calendar-availability						
+
+PROPFIND  /calendars/__uids__/<uid>/			- poll_calendar_home_depth1_propfind
+
+PROPPATCH /calendars/__uids__/<uid>/			- startup_calendarhome_default_alarm_date_proppatch
+PROPPATCH /calendars/__uids__/<uid>/			- startup_calendarhome_default_alarm_datetime_proppatch
+
+PROPPATCH /calendars/__uids__/<uid>/calendar/	- startup_calendar_order_proppatch
+PROPPATCH /calendars/__uids__/<uid>/calendar/	- startup_calendar_displayname_proppatch
+PROPPATCH /calendars/__uids__/<uid>/calendar/	- startup_calendar_color_proppatch
+PROPPATCH /calendars/__uids__/<uid>/calendar/	- startup_calendar_timezone_proppatch
+
+PROPPATCH /calendars/__uids__/<uid>/tasks/		- startup_calendar_order_proppatch
+PROPPATCH /calendars/__uids__/<uid>/tasks/		- startup_calendar_displayname_proppatch
+PROPPATCH /calendars/__uids__/<uid>/tasks/		- startup_calendar_color_proppatch
+PROPPATCH /calendars/__uids__/<uid>/tasks/		- startup_calendar_timezone_proppatch
+
+PROPFIND  /calendars/__uids__/<uid>/calendar/	- poll_calendar_propfind
+
+REPORT   /calendars/__uids__/<uid>/calendar/ 	- startup_query_events_depth1_report.request
+
+PROPFIND  /calendars/__uids__/<uid>/calendar/	- poll_calendar_propfind
+PROPFIND  /calendars/__uids__/<uid>/calendar/	- poll_calendar_depth1_propfind
+
+PROPFIND  /calendars/__uids__/<uid>/tasks/		- poll_calendar_propfind
+PROPFIND  /calendars/__uids__/<uid>/tasks/		- poll_calendar_depth1_propfind
+PROPFIND  /calendars/__uids__/<uid>/inbox/		- poll_calendar_propfind
+PROPFIND  /calendars/__uids__/<uid>/inbox/		- poll_calendar_depth1_propfind
+PROPFIND  /calendars/__uids__/<uid>/tasks/		- poll_calendar_propfind
+PROPFIND  /calendars/__uids__/<uid>/tasks/		- poll_calendar_depth1_propfind
+PROPFIND  /calendars/__uids__/<uid>/notification/	- poll_calendar_propfind
+PROPFIND  /calendars/__uids__/<uid>/notification/	- poll_notification_depth1_propfind
+
+REPORT    /principals/__uids__/<uid>/
+	calendar-proxy-write-for
+		calendar-user-address-set
+		email-address-set
+		displayname
+	calendar-proxy-read-for
+		calendar-user-address-set
+		email-address-set
+		displayname
+
+REPORT    /calendars/__uids__/<uid>/
+	sync-collection
+		sync-token
+		sync-level
+		*lots of properties*
+
+PROPFIND  /calendars/__uids__/<uid>/inbox/
+	getctag
+	sync-token
+
+PROPFIND  /principals/__uids__/<uid>/
+	calendar-proxy-write-for
+		calendar-user-address-set
+		email-address-set
+		displayname
+	calendar-proxy-read-for
+		calendar-user-address-set
+		email-address-set
+		displayname

Added: CalendarServer/trunk/contrib/performance/loadtest/request-data/OS_X_10_11/notification_multiget_report_hrefs.request
===================================================================
--- CalendarServer/trunk/contrib/performance/loadtest/request-data/OS_X_10_11/notification_multiget_report_hrefs.request	                        (rev 0)
+++ CalendarServer/trunk/contrib/performance/loadtest/request-data/OS_X_10_11/notification_multiget_report_hrefs.request	2015-11-06 22:41:28 UTC (rev 15297)
@@ -0,0 +1 @@
+  <A:href xmlns:A="DAV:">%(href)s</A:href>

Added: CalendarServer/trunk/contrib/performance/loadtest/request-data/OS_X_10_11/notification_sync.request
===================================================================
--- CalendarServer/trunk/contrib/performance/loadtest/request-data/OS_X_10_11/notification_sync.request	                        (rev 0)
+++ CalendarServer/trunk/contrib/performance/loadtest/request-data/OS_X_10_11/notification_sync.request	2015-11-06 22:41:28 UTC (rev 15297)
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<A:sync-collection xmlns:A="DAV:">
+  <A:sync-token>%(sync-token)s</A:sync-token>
+  <A:sync-level>1</A:sync-level>
+  <A:prop>
+    <C:notificationtype xmlns:C="http://calendarserver.org/ns/"/>
+    <A:getetag/>
+  </A:prop>
+</A:sync-collection>

Added: CalendarServer/trunk/contrib/performance/loadtest/request-data/OS_X_10_11/poll_calendar_depth1_propfind.request
===================================================================
--- CalendarServer/trunk/contrib/performance/loadtest/request-data/OS_X_10_11/poll_calendar_depth1_propfind.request	                        (rev 0)
+++ CalendarServer/trunk/contrib/performance/loadtest/request-data/OS_X_10_11/poll_calendar_depth1_propfind.request	2015-11-06 22:41:28 UTC (rev 15297)
@@ -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_11/poll_calendar_propfind.request
===================================================================
--- CalendarServer/trunk/contrib/performance/loadtest/request-data/OS_X_10_11/poll_calendar_propfind.request	                        (rev 0)
+++ CalendarServer/trunk/contrib/performance/loadtest/request-data/OS_X_10_11/poll_calendar_propfind.request	2015-11-06 22:41:28 UTC (rev 15297)
@@ -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/OS_X_10_11/poll_calendarhome_depth1_propfind.request
===================================================================
--- CalendarServer/trunk/contrib/performance/loadtest/request-data/OS_X_10_11/poll_calendarhome_depth1_propfind.request	                        (rev 0)
+++ CalendarServer/trunk/contrib/performance/loadtest/request-data/OS_X_10_11/poll_calendarhome_depth1_propfind.request	2015-11-06 22:41:28 UTC (rev 15297)
@@ -0,0 +1,43 @@
+<?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/"/>
+    <D:autoprovisioned xmlns:D="http://apple.com/ns/ical/"/>
+    <E:bulk-requests xmlns:E="http://me.com/_namespace/"/>
+    <B:calendar-alarm xmlns:B="urn:ietf:params:xml:ns:caldav"/>
+    <D:calendar-color xmlns:D="http://apple.com/ns/ical/"/>
+    <B:calendar-description xmlns:B="urn:ietf:params:xml:ns:caldav"/>
+    <B:calendar-free-busy-set xmlns:B="urn:ietf:params:xml:ns:caldav"/>
+    <D:calendar-order xmlns:D="http://apple.com/ns/ical/"/>
+    <B:calendar-timezone xmlns:B="urn:ietf:params:xml:ns:caldav"/>
+    <A:current-user-privilege-set/>
+    <B:default-alarm-vevent-date xmlns:B="urn:ietf:params:xml:ns:caldav"/>
+    <B:default-alarm-vevent-datetime xmlns:B="urn:ietf:params:xml:ns:caldav"/>
+    <A:displayname/>
+    <C:getctag xmlns:C="http://calendarserver.org/ns/"/>
+    <C:invite xmlns:C="http://calendarserver.org/ns/"/>
+    <D:language-code xmlns:D="http://apple.com/ns/ical/"/>
+    <D:location-code xmlns:D="http://apple.com/ns/ical/"/>
+    <A:owner/>
+    <C:pre-publish-url xmlns:C="http://calendarserver.org/ns/"/>
+    <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/>
+    <D:refreshrate xmlns:D="http://apple.com/ns/ical/"/>
+    <A:resource-id/>
+    <A:resourcetype/>
+    <B:schedule-calendar-transp xmlns:B="urn:ietf:params:xml:ns:caldav"/>
+    <B:schedule-default-calendar-URL xmlns:B="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/"/>
+    <B:supported-calendar-component-set xmlns:B="urn:ietf:params:xml:ns:caldav"/>
+    <B:supported-calendar-component-sets xmlns:B="urn:ietf:params:xml:ns:caldav"/>
+    <A:supported-report-set/>
+    <A:sync-token/>
+  </A:prop>
+</A:propfind>

Added: CalendarServer/trunk/contrib/performance/loadtest/request-data/OS_X_10_11/poll_calendarhome_sync.request
===================================================================
--- CalendarServer/trunk/contrib/performance/loadtest/request-data/OS_X_10_11/poll_calendarhome_sync.request	                        (rev 0)
+++ CalendarServer/trunk/contrib/performance/loadtest/request-data/OS_X_10_11/poll_calendarhome_sync.request	2015-11-06 22:41:28 UTC (rev 15297)
@@ -0,0 +1,45 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<A:sync-collection xmlns:A="DAV:">
+  <A:sync-token>data:,30_1122/8bbf7c540e5fca2cc3220f114a8164f7</A:sync-token>
+  <A:sync-level>1</A:sync-level>
+  <A:prop>
+    <C:publish-url xmlns:C="http://calendarserver.org/ns/"/>
+    <B:supported-calendar-component-sets xmlns:B="urn:ietf:params:xml:ns:caldav"/>
+    <B:schedule-calendar-transp xmlns:B="urn:ietf:params:xml:ns:caldav"/>
+    <C:source xmlns:C="http://calendarserver.org/ns/"/>
+    <B:calendar-description xmlns:B="urn:ietf:params:xml:ns:caldav"/>
+    <D:location-code xmlns:D="http://apple.com/ns/ical/"/>
+    <D:autoprovisioned xmlns:D="http://apple.com/ns/ical/"/>
+    <A:quota-used-bytes/>
+    <C:pre-publish-url xmlns:C="http://calendarserver.org/ns/"/>
+    <D:calendar-order xmlns:D="http://apple.com/ns/ical/"/>
+    <D:refreshrate xmlns:D="http://apple.com/ns/ical/"/>
+    <C:subscribed-strip-attachments xmlns:C="http://calendarserver.org/ns/"/>
+    <C:push-transports xmlns:C="http://calendarserver.org/ns/"/>
+    <B:schedule-default-calendar-URL xmlns:B="urn:ietf:params:xml:ns:caldav"/>
+    <B:calendar-alarm xmlns:B="urn:ietf:params:xml:ns:caldav"/>
+    <B:supported-calendar-component-set xmlns:B="urn:ietf:params:xml:ns:caldav"/>
+    <A:owner/>
+    <A:add-member/>
+    <C:invite xmlns:C="http://calendarserver.org/ns/"/>
+    <A:resource-id/>
+    <E:bulk-requests xmlns:E="http://me.com/_namespace/"/>
+    <B:calendar-timezone xmlns:B="urn:ietf:params:xml:ns:caldav"/>
+    <A:supported-report-set/>
+    <A:displayname/>
+    <C:subscribed-strip-alarms xmlns:C="http://calendarserver.org/ns/"/>
+    <D:language-code xmlns:D="http://apple.com/ns/ical/"/>
+    <C:subscribed-strip-todos xmlns:C="http://calendarserver.org/ns/"/>
+    <C:allowed-sharing-modes xmlns:C="http://calendarserver.org/ns/"/>
+    <A:current-user-privilege-set/>
+    <B:calendar-free-busy-set xmlns:B="urn:ietf:params:xml:ns:caldav"/>
+    <A:sync-token/>
+    <A:quota-available-bytes/>
+    <C:pushkey xmlns:C="http://calendarserver.org/ns/"/>
+    <B:default-alarm-vevent-date xmlns:B="urn:ietf:params:xml:ns:caldav"/>
+    <A:resourcetype/>
+    <B:default-alarm-vevent-datetime xmlns:B="urn:ietf:params:xml:ns:caldav"/>
+    <D:calendar-color xmlns:D="http://apple.com/ns/ical/"/>
+    <C:getctag xmlns:C="http://calendarserver.org/ns/"/>
+  </A:prop>
+</A:sync-collection>

Added: CalendarServer/trunk/contrib/performance/loadtest/request-data/OS_X_10_11/poll_notification_depth1_propfind.request
===================================================================
--- CalendarServer/trunk/contrib/performance/loadtest/request-data/OS_X_10_11/poll_notification_depth1_propfind.request	                        (rev 0)
+++ CalendarServer/trunk/contrib/performance/loadtest/request-data/OS_X_10_11/poll_notification_depth1_propfind.request	2015-11-06 22:41:28 UTC (rev 15297)
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<A:propfind xmlns:A="DAV:">
+  <A:prop>
+    <A:getetag/>
+    <C:notificationtype xmlns:C="http://calendarserver.org/ns/"/>
+  </A:prop>
+</A:propfind>

Added: CalendarServer/trunk/contrib/performance/loadtest/request-data/OS_X_10_11/post_freebusy.request
===================================================================
--- CalendarServer/trunk/contrib/performance/loadtest/request-data/OS_X_10_11/post_freebusy.request	                        (rev 0)
+++ CalendarServer/trunk/contrib/performance/loadtest/request-data/OS_X_10_11/post_freebusy.request	2015-11-06 22:41:28 UTC (rev 15297)
@@ -0,0 +1,13 @@
+BEGIN:VCALENDAR
+VERSION:2.0
+METHOD:REPLY
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VFREEBUSY
+UID:4288F0F3-5C5B-4DF4-9AD8-B1E5FE3F5B97
+DTSTART:20150804T211500Z
+DTEND:20150804T231500Z
+ATTENDEE:urn:uuid:30000000-0000-0000-0000-000000000005
+DTSTAMP:20150727T203410Z
+ORGANIZER:mailto:user01 at example.com
+END:VFREEBUSY
+END:VCALENDAR
\ No newline at end of file

Added: CalendarServer/trunk/contrib/performance/loadtest/request-data/OS_X_10_11/principal_search_report.request
===================================================================
--- CalendarServer/trunk/contrib/performance/loadtest/request-data/OS_X_10_11/principal_search_report.request	                        (rev 0)
+++ CalendarServer/trunk/contrib/performance/loadtest/request-data/OS_X_10_11/principal_search_report.request	2015-11-06 22:41:28 UTC (rev 15297)
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<C:calendarserver-principal-search xmlns:C="http://calendarserver.org/ns/" context="{context}">
+  {searchTokens}
+  <A:prop xmlns:A="DAV:">
+    <C:email-address-set/>
+    <B:calendar-user-type xmlns:B="urn:ietf:params:xml:ns:caldav"/>
+    <B:calendar-user-address-set xmlns:B="urn:ietf:params:xml:ns:caldav"/>
+    <A:principal-URL/>
+    <C:last-name/>
+    <C:record-type/>
+    <A:displayname/>
+    <C:first-name/>
+  </A:prop>
+</C:calendarserver-principal-search>
\ No newline at end of file

Added: CalendarServer/trunk/contrib/performance/loadtest/request-data/OS_X_10_11/report_principal_search.request
===================================================================
--- CalendarServer/trunk/contrib/performance/loadtest/request-data/OS_X_10_11/report_principal_search.request	                        (rev 0)
+++ CalendarServer/trunk/contrib/performance/loadtest/request-data/OS_X_10_11/report_principal_search.request	2015-11-06 22:41:28 UTC (rev 15297)
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<C:calendarserver-principal-search xmlns:C="http://calendarserver.org/ns/" context="attendee">
+  <C:search-token>%(search)s</C:search-token>
+  <A:prop xmlns:A="DAV:">
+    <B:calendar-user-type xmlns:B="urn:ietf:params:xml:ns:caldav"/>
+    <C:email-address-set/>
+    <A:displayname/>
+    <C:first-name/>
+    <C:last-name/>
+    <A:principal-URL/>
+    <C:record-type/>
+    <B:calendar-user-address-set xmlns:B="urn:ietf:params:xml:ns:caldav"/>
+  </A:prop>
+</C:calendarserver-principal-search>
\ No newline at end of file

Added: CalendarServer/trunk/contrib/performance/loadtest/request-data/OS_X_10_11/startup_calendar_color_proppatch.request
===================================================================
--- CalendarServer/trunk/contrib/performance/loadtest/request-data/OS_X_10_11/startup_calendar_color_proppatch.request	                        (rev 0)
+++ CalendarServer/trunk/contrib/performance/loadtest/request-data/OS_X_10_11/startup_calendar_color_proppatch.request	2015-11-06 22:41:28 UTC (rev 15297)
@@ -0,0 +1,2 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<A:propertyupdate xmlns:A="DAV:"><A:set><A:prop><D:calendar-color xmlns:D="http://apple.com/ns/ical/" symbolic-color="orange">#FD8208FF</D:calendar-color></A:prop></A:set></A:propertyupdate>
\ No newline at end of file

Added: CalendarServer/trunk/contrib/performance/loadtest/request-data/OS_X_10_11/startup_calendar_description_proppatch.request.xml
===================================================================
--- CalendarServer/trunk/contrib/performance/loadtest/request-data/OS_X_10_11/startup_calendar_description_proppatch.request.xml	                        (rev 0)
+++ CalendarServer/trunk/contrib/performance/loadtest/request-data/OS_X_10_11/startup_calendar_description_proppatch.request.xml	2015-11-06 22:41:28 UTC (rev 15297)
@@ -0,0 +1,2 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<A:propertyupdate xmlns:A="DAV:"><A:set><A:prop><B:calendar-description xmlns:B="urn:ietf:params:xml:ns:caldav">some description</B:calendar-description></A:prop></A:set></A:propertyupdate>
\ No newline at end of file

Added: CalendarServer/trunk/contrib/performance/loadtest/request-data/OS_X_10_11/startup_calendar_displayname_proppatch.request
===================================================================
--- CalendarServer/trunk/contrib/performance/loadtest/request-data/OS_X_10_11/startup_calendar_displayname_proppatch.request	                        (rev 0)
+++ CalendarServer/trunk/contrib/performance/loadtest/request-data/OS_X_10_11/startup_calendar_displayname_proppatch.request	2015-11-06 22:41:28 UTC (rev 15297)
@@ -0,0 +1,2 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<A:propertyupdate xmlns:A="DAV:"><A:set><A:prop><A:displayname>calendar</A:displayname></A:prop></A:set></A:propertyupdate>
\ No newline at end of file

Added: CalendarServer/trunk/contrib/performance/loadtest/request-data/OS_X_10_11/startup_calendar_order_proppatch.request
===================================================================
--- CalendarServer/trunk/contrib/performance/loadtest/request-data/OS_X_10_11/startup_calendar_order_proppatch.request	                        (rev 0)
+++ CalendarServer/trunk/contrib/performance/loadtest/request-data/OS_X_10_11/startup_calendar_order_proppatch.request	2015-11-06 22:41:28 UTC (rev 15297)
@@ -0,0 +1,2 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<A:propertyupdate xmlns:A="DAV:"><A:set><A:prop><D:calendar-order xmlns:D="http://apple.com/ns/ical/">1</D:calendar-order></A:prop></A:set></A:propertyupdate>

Added: CalendarServer/trunk/contrib/performance/loadtest/request-data/OS_X_10_11/startup_calendar_timezone_proppatch.request
===================================================================
--- CalendarServer/trunk/contrib/performance/loadtest/request-data/OS_X_10_11/startup_calendar_timezone_proppatch.request	                        (rev 0)
+++ CalendarServer/trunk/contrib/performance/loadtest/request-data/OS_X_10_11/startup_calendar_timezone_proppatch.request	2015-11-06 22:41:28 UTC (rev 15297)
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<A:propertyupdate xmlns:A="DAV:"><A:set><A:prop><B:calendar-timezone xmlns:B="urn:ietf:params:xml:ns:caldav">BEGIN:VCALENDAR&#13;
+VERSION:2.0&#13;
+PRODID:-//Apple Inc.//Mac OS X 10.11//EN&#13;
+CALSCALE:GREGORIAN&#13;
+BEGIN:VTIMEZONE&#13;
+TZID:America/Los_Angeles&#13;
+BEGIN:DAYLIGHT&#13;
+TZOFFSETFROM:-0800&#13;
+RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=2SU&#13;
+DTSTART:20070311T020000&#13;
+TZNAME:PDT&#13;
+TZOFFSETTO:-0700&#13;
+END:DAYLIGHT&#13;
+BEGIN:STANDARD&#13;
+TZOFFSETFROM:-0700&#13;
+RRULE:FREQ=YEARLY;BYMONTH=11;BYDAY=1SU&#13;
+DTSTART:20071104T020000&#13;
+TZNAME:PST&#13;
+TZOFFSETTO:-0800&#13;
+END:STANDARD&#13;
+END:VTIMEZONE&#13;
+END:VCALENDAR&#13;
+</B:calendar-timezone></A:prop></A:set></A:propertyupdate>
\ No newline at end of file

Added: CalendarServer/trunk/contrib/performance/loadtest/request-data/OS_X_10_11/startup_calendar_transparent_proppatch.request
===================================================================
--- CalendarServer/trunk/contrib/performance/loadtest/request-data/OS_X_10_11/startup_calendar_transparent_proppatch.request	                        (rev 0)
+++ CalendarServer/trunk/contrib/performance/loadtest/request-data/OS_X_10_11/startup_calendar_transparent_proppatch.request	2015-11-06 22:41:28 UTC (rev 15297)
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<A:propertyupdate xmlns:A="DAV:"><A:set><A:prop><B:schedule-calendar-transp xmlns:B="urn:ietf:params:xml:ns:caldav"><B:transparent/></B:schedule-calendar-transp></A:prop></A:set></A:propertyupdate>
+
+<?xml version="1.0" encoding="UTF-8"?>
+<A:propertyupdate xmlns:A="DAV:"><A:set><A:prop><B:schedule-calendar-transp xmlns:B="urn:ietf:params:xml:ns:caldav"><B:opaque/></B:schedule-calendar-transp></A:prop></A:set></A:propertyupdate>
\ No newline at end of file

Added: CalendarServer/trunk/contrib/performance/loadtest/request-data/OS_X_10_11/startup_calendarhome_default_alarm_date_proppatch.request
===================================================================
--- CalendarServer/trunk/contrib/performance/loadtest/request-data/OS_X_10_11/startup_calendarhome_default_alarm_date_proppatch.request	                        (rev 0)
+++ CalendarServer/trunk/contrib/performance/loadtest/request-data/OS_X_10_11/startup_calendarhome_default_alarm_date_proppatch.request	2015-11-06 22:41:28 UTC (rev 15297)
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<A:propertyupdate xmlns:A="DAV:"><A:set><A:prop><B:default-alarm-vevent-date xmlns:B="urn:ietf:params:xml:ns:caldav">BEGIN:VALARM&#13;
+X-WR-ALARMUID:49F29226-D2D7-4464-AE22-0147EDEFB2B4&#13;
+UID:49F29226-D2D7-4464-AE22-0147EDEFB2B4&#13;
+TRIGGER:-PT15H&#13;
+ATTACH;VALUE=URI:Basso&#13;
+ACTION:AUDIO&#13;
+END:VALARM&#13;
+</B:default-alarm-vevent-date></A:prop></A:set></A:propertyupdate>

Added: CalendarServer/trunk/contrib/performance/loadtest/request-data/OS_X_10_11/startup_calendarhome_default_alarm_datetime_proppatch.request
===================================================================
--- CalendarServer/trunk/contrib/performance/loadtest/request-data/OS_X_10_11/startup_calendarhome_default_alarm_datetime_proppatch.request	                        (rev 0)
+++ CalendarServer/trunk/contrib/performance/loadtest/request-data/OS_X_10_11/startup_calendarhome_default_alarm_datetime_proppatch.request	2015-11-06 22:41:28 UTC (rev 15297)
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<A:propertyupdate xmlns:A="DAV:"><A:set><A:prop><B:default-alarm-vevent-datetime xmlns:B="urn:ietf:params:xml:ns:caldav">BEGIN:VALARM&#13;
+X-WR-ALARMUID:4AD03A33-54A6-42BE-A157-47273DD60803&#13;
+UID:4AD03A33-54A6-42BE-A157-47273DD60803&#13;
+TRIGGER;VALUE=DATE-TIME:19760401T005545Z&#13;
+ACTION:NONE&#13;
+END:VALARM&#13;
+</B:default-alarm-vevent-datetime></A:prop></A:set></A:propertyupdate>

Added: CalendarServer/trunk/contrib/performance/loadtest/request-data/OS_X_10_11/startup_create_calendar.request
===================================================================
--- CalendarServer/trunk/contrib/performance/loadtest/request-data/OS_X_10_11/startup_create_calendar.request	                        (rev 0)
+++ CalendarServer/trunk/contrib/performance/loadtest/request-data/OS_X_10_11/startup_create_calendar.request	2015-11-06 22:41:28 UTC (rev 15297)
@@ -0,0 +1,39 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<B:mkcalendar xmlns:B="urn:ietf:params:xml:ns:caldav">
+  <A:set xmlns:A="DAV:">
+    <A:prop>
+      <D:calendar-order xmlns:D="http://apple.com/ns/ical/">{order}</D:calendar-order>
+      <B:supported-calendar-component-set>
+        <B:comp name="{component_type}"/>
+      </B:supported-calendar-component-set>
+      <D:calendar-color xmlns:D="http://apple.com/ns/ical/" symbolic-color="custom">#{color}</D:calendar-color>
+      <B:calendar-timezone>BEGIN:VCALENDAR&#13;
+VERSION:2.0&#13;
+PRODID:-//Apple Inc.//Mac OS X 10.11//EN&#13;
+CALSCALE:GREGORIAN&#13;
+BEGIN:VTIMEZONE&#13;
+TZID:America/Los_Angeles&#13;
+BEGIN:DAYLIGHT&#13;
+TZOFFSETFROM:-0800&#13;
+RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=2SU&#13;
+DTSTART:20070311T020000&#13;
+TZNAME:PDT&#13;
+TZOFFSETTO:-0700&#13;
+END:DAYLIGHT&#13;
+BEGIN:STANDARD&#13;
+TZOFFSETFROM:-0700&#13;
+RRULE:FREQ=YEARLY;BYMONTH=11;BYDAY=1SU&#13;
+DTSTART:20071104T020000&#13;
+TZNAME:PST&#13;
+TZOFFSETTO:-0800&#13;
+END:STANDARD&#13;
+END:VTIMEZONE&#13;
+END:VCALENDAR&#13;
+</B:calendar-timezone>
+      <A:displayname>{name}</A:displayname>
+      <B:schedule-calendar-transp>
+        <B:opaque/>
+      </B:schedule-calendar-transp>
+    </A:prop>
+  </A:set>
+</B:mkcalendar>
\ No newline at end of file

Added: CalendarServer/trunk/contrib/performance/loadtest/request-data/OS_X_10_11/startup_delegate_principal_propfind.request
===================================================================
--- CalendarServer/trunk/contrib/performance/loadtest/request-data/OS_X_10_11/startup_delegate_principal_propfind.request	                        (rev 0)
+++ CalendarServer/trunk/contrib/performance/loadtest/request-data/OS_X_10_11/startup_delegate_principal_propfind.request	2015-11-06 22:41:28 UTC (rev 15297)
@@ -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_11/startup_principal_expand.request
===================================================================
--- CalendarServer/trunk/contrib/performance/loadtest/request-data/OS_X_10_11/startup_principal_expand.request	                        (rev 0)
+++ CalendarServer/trunk/contrib/performance/loadtest/request-data/OS_X_10_11/startup_principal_expand.request	2015-11-06 22:41:28 UTC (rev 15297)
@@ -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_11/startup_principal_initial_propfind.request
===================================================================
--- CalendarServer/trunk/contrib/performance/loadtest/request-data/OS_X_10_11/startup_principal_initial_propfind.request	                        (rev 0)
+++ CalendarServer/trunk/contrib/performance/loadtest/request-data/OS_X_10_11/startup_principal_initial_propfind.request	2015-11-06 22:41:28 UTC (rev 15297)
@@ -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_11/startup_principal_propfind.request
===================================================================
--- CalendarServer/trunk/contrib/performance/loadtest/request-data/OS_X_10_11/startup_principal_propfind.request	                        (rev 0)
+++ CalendarServer/trunk/contrib/performance/loadtest/request-data/OS_X_10_11/startup_principal_propfind.request	2015-11-06 22:41:28 UTC (rev 15297)
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<A:propfind xmlns:A="DAV:">
+  <A:prop>
+    <B:calendar-home-set xmlns:B="urn:ietf:params:xml:ns:caldav"/>
+    <B:calendar-user-address-set xmlns:B="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/>
+    <B:schedule-inbox-URL xmlns:B="urn:ietf:params:xml:ns:caldav"/>
+    <B:schedule-outbox-URL xmlns:B="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_11/startup_principals_report.request
===================================================================
--- CalendarServer/trunk/contrib/performance/loadtest/request-data/OS_X_10_11/startup_principals_report.request	                        (rev 0)
+++ CalendarServer/trunk/contrib/performance/loadtest/request-data/OS_X_10_11/startup_principals_report.request	2015-11-06 22:41:28 UTC (rev 15297)
@@ -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/OS_X_10_11/startup_query_events_depth1_report.request
===================================================================
--- CalendarServer/trunk/contrib/performance/loadtest/request-data/OS_X_10_11/startup_query_events_depth1_report.request	                        (rev 0)
+++ CalendarServer/trunk/contrib/performance/loadtest/request-data/OS_X_10_11/startup_query_events_depth1_report.request	2015-11-06 22:41:28 UTC (rev 15297)
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<B:calendar-query xmlns:B="urn:ietf:params:xml:ns:caldav">
+  <A:prop xmlns:A="DAV:">
+    <A:getetag/>
+    <A:getcontenttype/>
+  </A:prop>
+  <B:filter>
+    <B:comp-filter name="VCALENDAR">
+      <B:comp-filter name="VEVENT">
+        <B:time-range start="20150630T010101Z" end="20150721T010101Z"/>
+      </B:comp-filter>
+    </B:comp-filter>
+  </B:filter>
+</B:calendar-query>

Added: CalendarServer/trunk/contrib/performance/loadtest/request-data/OS_X_10_11/startup_well_known_propfind.request
===================================================================
--- CalendarServer/trunk/contrib/performance/loadtest/request-data/OS_X_10_11/startup_well_known_propfind.request	                        (rev 0)
+++ CalendarServer/trunk/contrib/performance/loadtest/request-data/OS_X_10_11/startup_well_known_propfind.request	2015-11-06 22:41:28 UTC (rev 15297)
@@ -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>

Modified: CalendarServer/trunk/contrib/performance/loadtest/sim.py
===================================================================
--- CalendarServer/trunk/contrib/performance/loadtest/sim.py	2015-11-06 22:37:43 UTC (rev 15296)
+++ CalendarServer/trunk/contrib/performance/loadtest/sim.py	2015-11-06 22:41:28 UTC (rev 15297)
@@ -285,7 +285,8 @@
             if 'clientDataSerialization' in config:
                 serializationPath = config['clientDataSerialization']['Path']
                 if not config['clientDataSerialization']['UseOldData']:
-                    shutil.rmtree(serializationPath)
+                    if isdir(serializationPath):
+                        shutil.rmtree(serializationPath)
                 serializationPath = config['clientDataSerialization']['Path']
                 if not isdir(serializationPath):
                     try:
@@ -412,6 +413,7 @@
         return CalendarClientSimulator(
             self.records,
             populator,
+            Random(),
             self.parameters,
             self.reactor,
             self.server,

Modified: CalendarServer/trunk/contrib/performance/loadtest/test_ical.py
===================================================================
--- CalendarServer/trunk/contrib/performance/loadtest/test_ical.py	2015-11-06 22:37:43 UTC (rev 15296)
+++ CalendarServer/trunk/contrib/performance/loadtest/test_ical.py	2015-11-06 22:41:28 UTC (rev 15297)
@@ -21,7 +21,7 @@
 from caldavclientlibrary.protocol.webdav.definitions import davxml
 
 from contrib.performance.httpclient import MemoryConsumer, StringProducer
-from contrib.performance.loadtest.ical import XMPPPush, Event, Calendar, OS_X_10_6
+from contrib.performance.loadtest.ical import XMPPPush, Event, Calendar, OS_X_10_11, NotificationCollection
 from contrib.performance.loadtest.sim import _DirectoryRecord
 
 from pycalendar.datetime import DateTime
@@ -32,7 +32,7 @@
 from twisted.python.failure import Failure
 from twisted.trial.unittest import TestCase
 from twisted.web.client import ResponseDone
-from twisted.web.http import OK, NO_CONTENT, CREATED, MULTI_STATUS
+from twisted.web.http import OK, NO_CONTENT, CREATED, MULTI_STATUS, NOT_FOUND, FORBIDDEN
 from twisted.web.http_headers import Headers
 
 from twistedcaldav.ical import Component
@@ -581,6 +581,7 @@
     <href>/calendars/__uids__/user01/notification/</href>
     <propstat>
       <prop>
+        <sync-token xmlns='DAV:'>SYNCTOKEN3</sync-token>
         <displayname>notification</displayname>
         <resourcetype>
           <collection/>
@@ -742,6 +743,8 @@
     <propstat>
       <prop>
         <getctag xmlns='http://calendarserver.org/ns/'>c2696540-4c4c-4a31-adaf-c99630776828#3</getctag>
+        <sync-token xmlns='DAV:'>SYNCTOKEN1</sync-token>
+
         <displayname>calendar</displayname>
         <calendar-color xmlns='http://apple.com/ns/ical/'>#0252D4FF</calendar-color>
         <calendar-order xmlns='http://apple.com/ns/ical/'>1</calendar-order>
@@ -1027,6 +1030,7 @@
     <propstat>
       <prop>
         <getctag xmlns='http://calendarserver.org/ns/'>a483dab3-1391-445b-b1c3-5ae9dfc81c2f#0</getctag>
+        <sync-token xmlns='DAV:'>SYNCTOKEN2</sync-token>
         <displayname>inbox</displayname>
         <supported-calendar-component-set xmlns='urn:ietf:params:xml:ns:caldav'>
           <comp name='VEVENT'/>
@@ -1153,9 +1157,9 @@
 
 
 
-class OS_X_10_6Mixin:
+class OS_X_10_11Mixin:
     """
-    Mixin for L{TestCase}s for L{OS_X_10_6}.
+    Mixin for L{TestCase}s for L{OS_X_10_11}.
     """
     def setUp(self):
         TimezoneCache.create()
@@ -1164,7 +1168,7 @@
         )
         serializePath = self.mktemp()
         os.mkdir(serializePath)
-        self.client = OS_X_10_6(
+        self.client = OS_X_10_11(
             None,
             "http://127.0.0.1",
             "/principals/users/%s/",
@@ -1185,9 +1189,9 @@
 
 
 
-class OS_X_10_6Tests(OS_X_10_6Mixin, TestCase):
+class OS_X_10_11Tests(OS_X_10_11Mixin, TestCase):
     """
-    Tests for L{OS_X_10_6}.
+    Tests for L{OS_X_10_11}.
     """
     def test_parsePrincipalPROPFINDResponse(self):
         """
@@ -1230,12 +1234,12 @@
 
     def test_extractCalendars(self):
         """
-        L{OS_X_10_6._extractCalendars} accepts a calendar home
+        L{OS_X_10_11._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(
+        calendars, notificationCollection = self.client._extractCalendars(
             self.client._parseMultiStatus(CALENDAR_HOME_PROPFIND_RESPONSE), home)
         calendars.sort(key=lambda cal: cal.resourceType)
         calendar, inbox = calendars
@@ -1243,20 +1247,22 @@
         self.assertEquals(calendar.resourceType, caldavxml.calendar)
         self.assertEquals(calendar.name, "calendar")
         self.assertEquals(calendar.url, "/calendars/__uids__/user01/calendar/")
-        self.assertEquals(calendar.changeToken, "c2696540-4c4c-4a31-adaf-c99630776828#3")
+        self.assertEquals(calendar.changeToken, "SYNCTOKEN1")
 
         self.assertEquals(inbox.resourceType, caldavxml.schedule_inbox)
         self.assertEquals(inbox.name, "inbox")
         self.assertEquals(inbox.url, "/calendars/__uids__/user01/inbox/")
-        self.assertEquals(inbox.changeToken, "a483dab3-1391-445b-b1c3-5ae9dfc81c2f#0")
+        self.assertEquals(inbox.changeToken, "SYNCTOKEN2")
 
+        self.assertEquals(notificationCollection.changeToken, "SYNCTOKEN3")
+
         self.assertEqual({}, self.client.xmpp)
 
 
     def test_extractCalendarsXMPP(self):
         """
         If there is XMPP push information in a calendar home PROPFIND response,
-        L{OS_X_10_6._extractCalendars} finds it and records it.
+        L{OS_X_10_11._extractCalendars} finds it and records it.
         """
         home = "/calendars/__uids__/user01/"
         self.client._extractCalendars(
@@ -1283,7 +1289,7 @@
     @inlineCallbacks
     def test_changeEventAttendee(self):
         """
-        OS_X_10_6.changeEventAttendee removes one attendee from an
+        OS_X_10_11.changeEventAttendee removes one attendee from an
         existing event and appends another.
         """
         requests = self.interceptRequests()
@@ -1317,7 +1323,7 @@
 
     def test_addEvent(self):
         """
-        L{OS_X_10_6.addEvent} PUTs the event passed to it to the
+        L{OS_X_10_11.addEvent} PUTs the event passed to it to the
         server and updates local state to reflect its existence.
         """
         requests = self.interceptRequests()
@@ -1359,7 +1365,7 @@
     @inlineCallbacks
     def test_addInvite(self):
         """
-        L{OS_X_10_6.addInvite} PUTs the event passed to it to the
+        L{OS_X_10_11.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.
@@ -1450,7 +1456,7 @@
 
     def test_deleteEvent(self):
         """
-        L{OS_X_10_6.deleteEvent} DELETEs the event at the relative
+        L{OS_X_10_11.deleteEvent} DELETEs the event at the relative
         URL passed to it and updates local state to reflect its
         removal.
         """
@@ -1467,7 +1473,7 @@
 
         expectedResponseCode, method, url = req
 
-        self.assertEqual(expectedResponseCode, NO_CONTENT)
+        self.assertEqual(expectedResponseCode, (NO_CONTENT, NOT_FOUND))
         self.assertEqual(method, 'DELETE')
         self.assertEqual(url, 'http://127.0.0.1' + event.url)
         self.assertIsInstance(url, str)
@@ -1484,9 +1490,9 @@
 
     def test_serialization(self):
         """
-        L{OS_X_10_6.serialize} properly generates a JSON document.
+        L{OS_X_10_11.serialize} properly generates a JSON document.
         """
-        clientPath = os.path.join(self.client.serializePath, "user91-OS_X_10.6")
+        clientPath = os.path.join(self.client.serializePath, "user91-OS_X_10.11")
         self.assertFalse(os.path.exists(clientPath))
         indexPath = os.path.join(clientPath, "index.json")
         self.assertFalse(os.path.exists(indexPath))
@@ -1535,14 +1541,32 @@
         self.client._calendars["/home/calendar/"].events["1.ics"] = events[0]
         self.client._calendars["/home/inbox/"].events["i1.ics"] = events[1]
 
+        self.client._notificationCollection = NotificationCollection("/home/notification", "123")
+
         self.client.serialize()
         self.assertTrue(os.path.exists(clientPath))
         self.assertTrue(os.path.exists(indexPath))
         def _normDict(d):
-            return dict([(k, sorted(v, key=lambda x: x["changeToken" if k == "calendars" else "url"]) if v else None,) for k, v in d.items()])
+            return dict([
+                (
+                    k,
+                    sorted(
+                        v,
+                        key=lambda x:
+                            x["changeToken" if k == "calendars" else "url"]
+                    ) if isinstance(v, list) else v,
+                )
+                for k, v in d.items()
+            ])
         with open(indexPath) as f:
             jdata = f.read()
+
         self.assertEqual(_normDict(json.loads(jdata)), _normDict(json.loads("""{
+  "notificationCollection": {
+      "url": "/home/notification",
+      "notifications": [],
+      "changeToken": "123"
+    },
   "calendars": [
     {
       "changeToken": "123",
@@ -1612,7 +1636,7 @@
 
     def test_deserialization(self):
         """
-        L{OS_X_10_6.deserailize} properly parses a JSON document.
+        L{OS_X_10_11.deserailize} properly parses a JSON document.
         """
 
         cal1 = """BEGIN:VCALENDAR
@@ -1645,7 +1669,7 @@
 END:VCALENDAR
 """.replace("\n", "\r\n")
 
-        clientPath = os.path.join(self.client.serializePath, "user91-OS_X_10.6")
+        clientPath = os.path.join(self.client.serializePath, "user91-OS_X_10.11")
         os.mkdir(clientPath)
         indexPath = os.path.join(clientPath, "index.json")
         with open(indexPath, "w") as f:
@@ -1738,32 +1762,32 @@
 
 
 
-class UpdateCalendarTests(OS_X_10_6Mixin, TestCase):
+class UpdateCalendarTests(OS_X_10_11Mixin, TestCase):
     """
-    Tests for L{OS_X_10_6._updateCalendar}.
+    Tests for L{OS_X_10_11._updateCalendar}.
     """
 
     _CALENDAR_PROPFIND_RESPONSE_BODY = """\
 <?xml version='1.0' encoding='UTF-8'?>
-<multistatus xmlns='DAV:'>
-  <response>
-    <href>/something/anotherthing.ics</href>
-    <propstat>
-      <prop>
-        <resourcetype>
-          <collection/>
-        </resourcetype>
-        <getetag>"None"</getetag>
-      </prop>
-      <status>HTTP/1.1 200 OK</status>
-    </propstat>
-    <propstat>
-      <prop>
-      </prop>
-      <status>HTTP/1.1 404 Not Found</status>
-    </propstat>
-  </response>
+<multistatus xmlns='DAV:'>
   <response>
+    <href>/something/anotherthing.ics</href>
+    <propstat>
+      <prop>
+        <resourcetype>
+          <collection/>
+        </resourcetype>
+        <getetag>"None"</getetag>
+      </prop>
+      <status>HTTP/1.1 200 OK</status>
+    </propstat>
+    <propstat>
+      <prop>
+      </prop>
+      <status>HTTP/1.1 404 Not Found</status>
+    </propstat>
+  </response>
+  <response>
     <href>/something/else.ics</href>
     <propstat>
       <prop>
@@ -1867,7 +1891,7 @@
 
     def test_eventMissing(self):
         """
-        If an event included in the calendar PROPFIND response no longer exists
+        If an event included in the calendar sync REPORT response no longer exists
         by the time a REPORT is issued for that event, the 404 is handled and
         the rest of the normal update logic for that event is skipped.
         """
@@ -1878,9 +1902,9 @@
         self.client._updateCalendar(calendar, "1234")
         result, req = requests.pop(0)
         expectedResponseCode, method, url, _ignore_headers, _ignore_body = req
-        self.assertEqual('PROPFIND', method)
+        self.assertEqual('REPORT', method)
         self.assertEqual('http://127.0.0.1/something/', url)
-        self.assertEqual((MULTI_STATUS,), expectedResponseCode)
+        self.assertEqual((MULTI_STATUS, FORBIDDEN), expectedResponseCode)
 
         result.callback(
             MemoryResponse(
@@ -1908,7 +1932,7 @@
 
     def test_multigetBatch(self):
         """
-        If an event included in the calendar PROPFIND response no longer exists
+        If an event included in the calendar sync REPORT response no longer exists
         by the time a REPORT is issued for that event, the 404 is handled and
         the rest of the normal update logic for that event is skipped.
         """
@@ -1921,9 +1945,9 @@
         self.client._updateCalendar(calendar, "1234")
         result, req = requests.pop(0)
         expectedResponseCode, method, url, _ignore_headers, _ignore_body = req
-        self.assertEqual('PROPFIND', method)
+        self.assertEqual('REPORT', method)
         self.assertEqual('http://127.0.0.1/something/', url)
-        self.assertEqual((MULTI_STATUS,), expectedResponseCode)
+        self.assertEqual((MULTI_STATUS, FORBIDDEN), expectedResponseCode)
 
         result.callback(
             MemoryResponse(
@@ -1960,13 +1984,13 @@
 
 
 
-class VFreeBusyTests(OS_X_10_6Mixin, TestCase):
+class VFreeBusyTests(OS_X_10_11Mixin, TestCase):
     """
-    Tests for L{OS_X_10_6.requestAvailability}.
+    Tests for L{OS_X_10_11.requestAvailability}.
     """
     def test_requestAvailability(self):
         """
-        L{OS_X_10_6.requestAvailability} accepts a date range and a set of
+        L{OS_X_10_11.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_profiles.py
===================================================================
--- CalendarServer/trunk/contrib/performance/loadtest/test_profiles.py	2015-11-06 22:37:43 UTC (rev 15296)
+++ CalendarServer/trunk/contrib/performance/loadtest/test_profiles.py	2015-11-06 22:41:28 UTC (rev 15297)
@@ -40,38 +40,38 @@
 import os
 
 SIMPLE_EVENT = """\
-BEGIN:VCALENDAR
-VERSION:2.0
-PRODID:-//Apple Inc.//iCal 4.0.3//EN
-CALSCALE:GREGORIAN
-BEGIN:VTIMEZONE
-TZID:America/New_York
-BEGIN:DAYLIGHT
-TZOFFSETFROM:-0500
-RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=2SU
-DTSTART:20070311T020000
-TZNAME:EDT
-TZOFFSETTO:-0400
-END:DAYLIGHT
-BEGIN:STANDARD
-TZOFFSETFROM:-0400
-RRULE:FREQ=YEARLY;BYMONTH=11;BYDAY=1SU
-DTSTART:20071104T020000
-TZNAME:EST
-TZOFFSETTO:-0500
-END:STANDARD
-END:VTIMEZONE
-BEGIN:VEVENT
-CREATED:20101018T155431Z
-UID:C98AD237-55AD-4F7D-9009-0D355D835822
-DTEND;TZID=America/New_York:20101021T130000
-TRANSP:OPAQUE
-SUMMARY:Simple event
-DTSTART;TZID=America/New_York:20101021T120000
-DTSTAMP:20101018T155438Z
-SEQUENCE:2
-END:VEVENT
-END:VCALENDAR
+BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//Apple Inc.//iCal 4.0.3//EN
+CALSCALE:GREGORIAN
+BEGIN:VTIMEZONE
+TZID:America/New_York
+BEGIN:DAYLIGHT
+TZOFFSETFROM:-0500
+RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=2SU
+DTSTART:20070311T020000
+TZNAME:EDT
+TZOFFSETTO:-0400
+END:DAYLIGHT
+BEGIN:STANDARD
+TZOFFSETFROM:-0400
+RRULE:FREQ=YEARLY;BYMONTH=11;BYDAY=1SU
+DTSTART:20071104T020000
+TZNAME:EST
+TZOFFSETTO:-0500
+END:STANDARD
+END:VTIMEZONE
+BEGIN:VEVENT
+CREATED:20101018T155431Z
+UID:C98AD237-55AD-4F7D-9009-0D355D835822
+DTEND;TZID=America/New_York:20101021T130000
+TRANSP:OPAQUE
+SUMMARY:Simple event
+DTSTART;TZID=America/New_York:20101021T120000
+DTSTAMP:20101018T155438Z
+SEQUENCE:2
+END:VEVENT
+END:VCALENDAR
 """
 
 INVITED_EVENT = """\
@@ -347,7 +347,7 @@
     """
     def setUp(self):
         self.sim = CalendarClientSimulator(
-            AnyUser(), Populator(None), None, None, None, None, None)
+            AnyUser(), Populator(None), None, None, None, None, None, None)
 
 
     def _simpleAccount(self, userNumber, eventText):
@@ -546,7 +546,7 @@
     """
     def setUp(self):
         self.sim = CalendarClientSimulator(
-            AnyUser(), Populator(None), None, None, None, None, None)
+            AnyUser(), Populator(None), None, None, None, None, None, None)
 
 
     def _simpleAccount(self, userNumber, eventText):
@@ -716,7 +716,7 @@
     """
     def setUp(self):
         self.sim = CalendarClientSimulator(
-            AnyUser(), Populator(None), None, None, None, None, None)
+            AnyUser(), Populator(None), None, None, None, None, None, None)
 
 
     def test_enabled(self):
@@ -983,7 +983,7 @@
     """
     def setUp(self):
         self.sim = CalendarClientSimulator(
-            AnyUser(), Populator(None), None, None, None, None, None)
+            AnyUser(), Populator(None), None, None, None, None, None, None)
 
 
     def test_enabled(self):

Modified: CalendarServer/trunk/contrib/performance/loadtest/test_sim.py
===================================================================
--- CalendarServer/trunk/contrib/performance/loadtest/test_sim.py	2015-11-06 22:37:43 UTC (rev 15296)
+++ CalendarServer/trunk/contrib/performance/loadtest/test_sim.py	2015-11-06 22:41:28 UTC (rev 15297)
@@ -121,7 +121,7 @@
         """
         calsim = CalendarClientSimulator(
             [self._user('alice'), self._user('bob'), self._user('carol')],
-            Populator(None), None, None, 'http://example.org:1234/', None, None)
+            Populator(None), None, None, None, 'http://example.org:1234/', None, None)
         users = sorted([
             calsim._createUser(0)[0],
             calsim._createUser(1)[0],
@@ -137,7 +137,7 @@
         """
         calsim = CalendarClientSimulator(
             [self._user('alice')],
-            Populator(None), None, None, 'http://example.org:1234/', None, None)
+            Populator(None), None, None, None, 'http://example.org:1234/', None, None)
         user, auth = calsim._createUser(0)
         self.assertEqual(
             auth['basic'].passwd.find_user_password('Test Realm', 'http://example.org:1234/')[1],
@@ -182,7 +182,7 @@
             [ProfileType(BrokenProfile, {'runResult': profileRunResult})])
         )
         sim = CalendarClientSimulator(
-            [self._user('alice')], Populator(None), params, None, 'http://example.com:1234/', None, None)
+            [self._user('alice')], Populator(None), None, params, None, 'http://example.com:1234/', None, None)
         sim.add(1, 1)
         sim.stop()
         clientRunResult.errback(RuntimeError("Some fictional client problem"))
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://lists.macosforge.org/pipermail/calendarserver-changes/attachments/20151106/666f68a8/attachment-0001.html>


More information about the calendarserver-changes mailing list