[CalendarServer-changes] [15588] CalendarServer/trunk

source_changes at macosforge.org source_changes at macosforge.org
Thu May 12 12:28:46 PDT 2016


Revision: 15588
          http://trac.calendarserver.org//changeset/15588
Author:   sagen at apple.com
Date:     2016-05-12 12:28:46 -0700 (Thu, 12 May 2016)
Log Message:
-----------
Client sim changes:  Eventer and Inviter now post attachments; by default each user has a secondary, passive, client which just syncs and downloads attachments; when we get an unexpected response code, the request and response are printed; managed attachments are fetched through the client's "home" server (or the server specified in the managed-attachments-server-URL property.

Modified Paths:
--------------
    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/request-data/OS_X_10_11/poll_calendarhome_depth1_propfind.request
    CalendarServer/trunk/contrib/performance/loadtest/test_ical.py
    CalendarServer/trunk/contrib/performance/loadtest/test_profiles.py
    CalendarServer/trunk/requirements-dev.txt

Modified: CalendarServer/trunk/contrib/performance/loadtest/clients.plist
===================================================================
--- CalendarServer/trunk/contrib/performance/loadtest/clients.plist	2016-05-11 16:03:38 UTC (rev 15587)
+++ CalendarServer/trunk/contrib/performance/loadtest/clients.plist	2016-05-12 19:28:46 UTC (rev 15588)
@@ -38,7 +38,7 @@
 				<dict>
 					<!-- Name that appears in logs. -->
 					<key>title</key>
-					<string>10.11</string>
+					<string>10.11a</string>
 
 					<!-- OS_X_10_11 can poll the calendar home at some interval. This is
 						in seconds. -->
@@ -163,6 +163,24 @@
 									</dict>
 								</dict>
 							</dict>
+
+							<!-- 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>
 
@@ -343,7 +361,7 @@
 						<key>params</key>
 						<dict>
 							<key>enabled</key>
-							<true/>
+							<false/>
 
 							<!-- Define the interval (in seconds) at which this profile will use
 								its client to create a new event. -->
@@ -578,6 +596,24 @@
 									</dict>
 								</dict>
 							</dict>
+
+							<!-- 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>
 
@@ -712,6 +748,58 @@
 				<key>weight</key>
 				<integer>1</integer>
 			</dict>
+
+			<dict>
+
+				<!-- Here is a more passive OS X client simulator. -->
+				<key>software</key>
+				<string>contrib.performance.loadtest.ical.OS_X_10_11</string>
+
+				<!-- 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.11b</string>
+
+					<!-- OS_X_10_11 can poll the calendar home at some interval. This is
+						in seconds. -->
+					<key>calendarHomePollInterval</key>
+					<integer>30</integer>
+
+					<!-- If the server advertises xmpp push, OS_X_10_7 can wait for notifications
+						about calendar home changes instead of polling for them periodically. If
+						this option is true, then look for the server advertisement for xmpp push
+						and use it if possible. Still fall back to polling if there is no xmpp push
+						advertised. -->
+					<key>supportPush</key>
+					<true/>
+
+					<key>supportAmpPush</key>
+					<true/>
+				</dict>
+
+				<!-- The profiles define certain types of user behavior on top of the
+					client software being simulated. -->
+				<key>profiles</key>
+				<array>
+					<!-- This profile downloads attachments when an event changes. -->
+					<dict>
+						<key>class</key>
+						<string>contrib.performance.loadtest.profiles.AttachmentDownloader</string>
+
+						<key>params</key>
+						<dict>
+							<key>enabled</key>
+							<true/>
+						</dict>
+					</dict>
+				</array>
+				<!-- Determine the frequency at which this client configuration will
+					appear in the clients which are created by the load tester. -->
+				<key>weight</key>
+				<integer>1</integer>
+			</dict>
 		</array>
 	</dict>
 </plist>

Modified: CalendarServer/trunk/contrib/performance/loadtest/config.plist
===================================================================
--- CalendarServer/trunk/contrib/performance/loadtest/config.plist	2016-05-11 16:03:38 UTC (rev 15587)
+++ CalendarServer/trunk/contrib/performance/loadtest/config.plist	2016-05-12 19:28:46 UTC (rev 15588)
@@ -157,7 +157,7 @@
 				<!-- Number of clients each user is assigned to. -->
 				<!-- Set weight of clients to 1 if this is > 1. Number of clients must match this value if > 1. -->
 				<key>clientsPerUser</key>
-				<integer>1</integer>
+				<integer>2</integer>
 			</dict>
 
 		</dict>
@@ -168,7 +168,8 @@
 			<!-- ReportStatistics generates an end-of-run summary of the HTTP requests
 				made, their timings, and their results. -->
 			<dict>
-				<key>type</key>
+			<key>type</key>
+
 				<string>contrib.performance.loadtest.population.ReportStatistics</string>
 				<key>params</key>
 				<dict>

Modified: CalendarServer/trunk/contrib/performance/loadtest/ical.py
===================================================================
--- CalendarServer/trunk/contrib/performance/loadtest/ical.py	2016-05-11 16:03:38 UTC (rev 15587)
+++ CalendarServer/trunk/contrib/performance/loadtest/ical.py	2016-05-12 19:28:46 UTC (rev 15588)
@@ -85,10 +85,14 @@
 
     @ivar response: The response which was received
     @type response: L{twisted.web.client.Response}
+
+    @ivar responseBody: The body of the received response
+    @type responseBody: C{str}
     """
-    def __init__(self, expected, response):
+    def __init__(self, expected, response, responseBody):
         self.expected = expected
         self.response = response
+        self.responseBody = responseBody
 
 
 
@@ -447,6 +451,8 @@
 
     _client_type = "Generic"
 
+    _managed_attachments_server_url = None
+
     USER_AGENT = None   # Override this for specific clients
 
     # The default interval, used if none is specified in external
@@ -618,9 +624,8 @@
         before = self.reactor.seconds()
         response = yield self.agent.request(method, url, headers, body)
 
-        # XXX This is time to receive response headers, not time
-        # to receive full response.  Should measure the latter, if
-        # not both.
+        responseBody = yield readBody(response)
+
         after = self.reactor.seconds()
 
         success = response.code in expectedResponseCodes
@@ -631,6 +636,7 @@
             method=method_label if method_label else method,
             headers=headers,
             body=body,
+            responseBody=responseBody,
             code=response.code,
             user=self.record.uid,
             client_type=self.title,
@@ -640,22 +646,30 @@
         )
 
         if success:
-            returnValue(response)
+            returnValue((response, responseBody))
 
-        raise IncorrectResponseCode(expectedResponseCodes, response)
+        raise IncorrectResponseCode(expectedResponseCodes, response, responseBody)
 
 
-    def _parseMultiStatus(self, response, otherTokens=False):
+    def _parseMultiStatus(self, responseBody, otherTokens=False):
         """
         Parse a <multistatus> - might need to return other top-level elements
         in the response - e.g. DAV:sync-token
         I{PROPFIND} request for the principal URL.
 
-        @type response: C{str}
+        @type responseBody: C{str}
         @rtype: C{cls}
         """
         parser = PropFindParser()
-        parser.parseData(response)
+        try:
+            parser.parseData(responseBody)
+        except:
+            print("=" * 80)
+            print("COULD NOT PARSE RESPONSE:")
+            print(responseBody)
+            print("=" * 80)
+            raise
+
         if otherTokens:
             return (parser.getResults(), parser.getOthers(),)
         else:
@@ -674,7 +688,7 @@
         hdrs = Headers({'content-type': ['text/xml']})
         if depth is not None:
             hdrs.addRawHeader('depth', depth)
-        response = yield self._request(
+        response, responseBody = yield self._request(
             allowedStatus,
             'PROPFIND',
             self.server["uri"] + url.encode('utf-8'),
@@ -683,8 +697,7 @@
             method_label=method_label,
         )
 
-        body = yield readBody(response)
-        result = self._parseMultiStatus(body) if response.code == MULTI_STATUS else None
+        result = self._parseMultiStatus(responseBody) if response.code == MULTI_STATUS else None
 
         returnValue((response, result,))
 
@@ -695,7 +708,7 @@
         Issue a PROPPATCH on the chosen URL
         """
         hdrs = Headers({'content-type': ['text/xml']})
-        response = yield self._request(
+        response, responseBody = yield self._request(
             (OK, MULTI_STATUS,),
             'PROPPATCH',
             self.server["uri"] + url.encode('utf-8'),
@@ -704,8 +717,7 @@
             method_label=method_label,
         )
         if response.code == MULTI_STATUS:
-            body = yield readBody(response)
-            result = self._parseMultiStatus(body)
+            result = self._parseMultiStatus(responseBody)
             returnValue(result)
         else:
             returnValue(None)
@@ -716,15 +728,14 @@
         """
         Issue a GET on the chosen URL
         """
-        response = yield self._request(
+        response, responseBody = yield self._request(
             allowedStatus,
             'GET',
             url,
             method_label=method_label,
         )
 
-        body = yield readBody(response)
-        returnValue(body)
+        returnValue(responseBody)
 
 
     @inlineCallbacks
@@ -735,7 +746,7 @@
         hdrs = Headers({'content-type': ['text/xml']})
         if depth is not None:
             hdrs.addRawHeader('depth', depth)
-        response = yield self._request(
+        response, responseBody = yield self._request(
             allowedStatus,
             'REPORT',
             self.server["uri"] + url.encode('utf-8'),
@@ -744,8 +755,7 @@
             method_label=method_label,
         )
 
-        body = yield readBody(response)
-        result = self._parseMultiStatus(body, otherTokens) if response.code == MULTI_STATUS else None
+        result = self._parseMultiStatus(responseBody, otherTokens) if response.code == MULTI_STATUS else None
 
         returnValue(result)
 
@@ -890,8 +900,20 @@
 
             if href == calendarHome:
                 text = results[href].getTextProperties()
+                hrefs = results[href].getHrefProperties()
 
+                # Extract managed attachments url
+                self._managed_attachments_server_url = None
                 try:
+                    url = hrefs[caldavxml.managed_attachments_server_url]
+                    if url.toString():
+                        self._managed_attachments_server_url = url.toString()
+                except KeyError:
+                    pass
+                if self._managed_attachments_server_url is None:
+                    self._managed_attachments_server_url = self.server['uri']
+
+                try:
                     pushkey = text[csxml.pushkey]
                 except KeyError:
                     pass
@@ -929,10 +951,9 @@
             if isCalendar:
                 textProps = results[href].getTextProperties()
                 componentTypes = set()
-                if nodeType.tag == caldavxml.calendar:
-                    if caldavxml.supported_calendar_component_set in nodes:
-                        for comp in nodes[caldavxml.supported_calendar_component_set]:
-                            componentTypes.add(comp.get("name").upper())
+                if caldavxml.supported_calendar_component_set in nodes:
+                    for comp in nodes[caldavxml.supported_calendar_component_set]:
+                        componentTypes.add(comp.get("name").upper())
 
                 calendars.append(Calendar(
                     resourceType,
@@ -1022,7 +1043,7 @@
                     method_label="REPORT{sync}" if calendar.changeToken else "REPORT{sync-init}",
                 )
             else:
-                raise IncorrectResponseCode((MULTI_STATUS,), None)
+                raise IncorrectResponseCode((MULTI_STATUS,), None, None)
 
         result, others = result
 
@@ -1208,7 +1229,7 @@
                     method_label="REPORT{sync}" if oldToken else "REPORT{sync-init}",
                 )
             else:
-                raise IncorrectResponseCode((MULTI_STATUS,), None)
+                raise IncorrectResponseCode((MULTI_STATUS,), None, None)
 
         result, _ignore_others = result
 
@@ -1229,24 +1250,24 @@
 
             if result[responseHref].getStatus() / 100 == 2:
                 # Get the notification
-                response = yield self._request(
-                    OK,
+                response, responseBody = yield self._request(
+                    (OK, NOT_FOUND),
                     'GET',
                     self.server["uri"] + 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
+                if response.code == OK:
+                    node = ElementTree(file=StringIO(responseBody)).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:
@@ -1287,7 +1308,7 @@
 
         # Delete all the notification resources
         for responseHref in toDelete:
-            response = yield self._request(
+            response, responseBody = yield self._request(
                 (NO_CONTENT, NOT_FOUND),
                 'DELETE',
                 self.server["uri"] + responseHref.encode('utf-8'),
@@ -1497,8 +1518,8 @@
 
 
     def _receivedPush(self, inboundID, dataChangedTimestamp, priority=5):
-        for href, id in self.ampPushKeys.iteritems():
-            if inboundID == id:
+        for href, myId in self.ampPushKeys.iteritems():
+            if inboundID == myId:
                 self._checkCalendarsForEvents(href, push=True)
                 break
         else:
@@ -1708,7 +1729,7 @@
             headers.removeHeader('if-match')
 
         # At last, upload the new event definition
-        response = yield self._request(
+        response, responseBody = yield self._request(
             (NO_CONTENT, PRECONDITION_FAILED,),
             'PUT',
             self.server["uri"] + href.encode('utf-8'),
@@ -1803,7 +1824,7 @@
         if len(attendees) > 75:
             label_suffix = "huge"
 
-        response = yield self._request(
+        response, responseBody = yield self._request(
             okCodes,
             'PUT',
             self.server["uri"] + href.encode('utf-8'),
@@ -1825,7 +1846,7 @@
 
         self._removeEvent(href)
 
-        response = yield self._request(
+        response, responseBody = yield self._request(
             (NO_CONTENT, NOT_FOUND),
             'DELETE',
             self.server["uri"] + href.encode('utf-8'),
@@ -1835,7 +1856,7 @@
 
 
     @inlineCallbacks
-    def addEvent(self, href, component, invite=False):
+    def addEvent(self, href, component, invite=False, attachmentSize=0):
         headers = Headers({
             'content-type': ['text/calendar'],
         })
@@ -1849,7 +1870,7 @@
         if len(attendees) > 75:
             label_suffix = "huge"
 
-        response = yield self._request(
+        response, responseBody = yield self._request(
             CREATED,
             'PUT',
             self.server["uri"] + href.encode('utf-8'),
@@ -1862,9 +1883,14 @@
         if not response.headers.hasHeader("etag"):
             response = yield self.updateEvent(href)
 
+        # Add optional attachment
+        if attachmentSize:
+            yield self.postAttachment(href, 'x' * attachmentSize)
 
+
+
     @inlineCallbacks
-    def addInvite(self, href, component):
+    def addInvite(self, href, component, attachmentSize=0):
         """
         Add an event that is an invite - i.e., has attendees. We will do attendee lookups and freebusy
         checks on each attendee to simulate what happens when an organizer creates a new invite.
@@ -1878,7 +1904,7 @@
             yield self._attendeeAutoComplete(component, attendee)
 
         # Now do a normal PUT
-        yield self.addEvent(href, component, invite=True)
+        yield self.addEvent(href, component, invite=True, attachmentSize=attachmentSize)
 
 
     @inlineCallbacks
@@ -1894,7 +1920,7 @@
             headers.removeHeader('if-match')
 
         # At last, upload the new event definition
-        response = yield self._request(
+        response, responseBody = yield self._request(
             (NO_CONTENT, PRECONDITION_FAILED,),
             'PUT',
             self.server["uri"] + href.encode('utf-8'),
@@ -1920,7 +1946,7 @@
 
     @inlineCallbacks
     def updateEvent(self, href):
-        response = yield self._request(
+        response, responseBody = yield self._request(
             OK,
             'GET',
             self.server["uri"] + href.encode('utf-8'),
@@ -1929,8 +1955,7 @@
         headers = response.headers
         etag = headers.getRawHeaders('etag')[0]
         scheduleTag = headers.getRawHeaders('schedule-tag', [None])[0]
-        body = yield readBody(response)
-        self.eventChanged(href, etag, scheduleTag, body)
+        self.eventChanged(href, etag, scheduleTag, responseBody)
         returnValue(response)
 
 
@@ -1988,7 +2013,7 @@
         if len(users) > 75:
             label_suffix = "huge"
 
-        response = yield self._request(
+        response, responseBody = yield self._request(
             OK, 'POST', outbox,
             Headers({
                     'content-type': ['text/calendar'],
@@ -2006,8 +2031,7 @@
             }),
             method_label="POST{fb-%s}" % (label_suffix,),
         )
-        body = yield readBody(response)
-        returnValue(body)
+        returnValue(responseBody)
 
 
     @inlineCallbacks
@@ -2017,15 +2041,14 @@
         headers = Headers({
             'Content-Disposition': ['attachment; filename="{}"'.format(filename)]
         })
-        response = yield self._request(
+        response, responseBody = yield self._request(
             CREATED,
             'POST',
             url,
             headers=headers,
             body=StringProducer(content),
-            method_label="POST{attach}"
+            method_label="POST{attachment}"
         )
-        body = yield readBody(response)
 
         # We don't want to download an attachment we uploaded, so look for the
         # Cal-Managed-Id: and Location: headers and remember those
@@ -2034,7 +2057,7 @@
         self._attachments[managedId] = location
 
         yield self.updateEvent(href)
-        returnValue(body)
+        returnValue(responseBody)
 
 
     @inlineCallbacks
@@ -2043,7 +2066,10 @@
         # If we've already downloaded this managedId, skip it.
         if managedId not in self._attachments:
             self._attachments[managedId] = href
-            yield self._newOperation("download", self._get(href, 200))
+            yield self._newOperation(
+                "download",
+                self._get(href, (OK, FORBIDDEN), method_label="GET{attachment}")
+            )
 
 
     @inlineCallbacks
@@ -2051,7 +2077,7 @@
         headers = Headers({
             'content-type': ['text/xml']
         })
-        response = yield self._request(
+        response, responseBody = yield self._request(
             (OK, CREATED, MULTI_STATUS),
             'POST',
             self.server["uri"] + href,
@@ -2059,8 +2085,7 @@
             body=StringProducer(content),
             method_label=label
         )
-        body = yield readBody(response)
-        returnValue(body)
+        returnValue(responseBody)
 
 
 
@@ -2496,7 +2521,7 @@
 
 
 class RequestLogger(object):
-    format = u"%(user)s request %(code)s%(success)s[%(duration)5.2f s] %(method)8s %(url)s"
+    format = u"%(user)s %(client)s request %(code)s%(success)s[%(duration)5.2f s] %(method)8s %(url)s"
     success = u"\N{CHECK MARK}"
     failure = u"\N{BALLOT X}"
 
@@ -2508,6 +2533,7 @@
                 url=urlunparse(('', '') + urlparse(event['url'])[2:]),
                 code=event['code'],
                 duration=event['duration'],
+                client=event['client_type'],
             )
 
             if event['success']:
@@ -2516,7 +2542,28 @@
                 formatArgs['success'] = self.failure
             print((self.format % formatArgs).encode('utf-8'))
 
+            if not event['success']:
+                body = event['body']
+                if isinstance(body, StringProducer):
+                    body = body._body
+                if body:
+                    body = body[:5000]
 
+                responseBody = event['responseBody']
+                if responseBody:
+                    responseBody = responseBody[:5000]
+
+                print("=" * 80)
+                print("INCORRECT ERROR CODE: {}".format(event['code']))
+                print("URL: {}".format(event['url']))
+                print("METHOD: {}".format(event['method']))
+                print("USER: {}".format(event['user']))
+                print("REQUEST BODY:\n{}".format(body))
+                print("RESPONSE BODY:\n{}".format(responseBody))
+                print("=" * 80)
+
+
+
     def report(self, output):
         pass
 

Modified: CalendarServer/trunk/contrib/performance/loadtest/population.py
===================================================================
--- CalendarServer/trunk/contrib/performance/loadtest/population.py	2016-05-11 16:03:38 UTC (rev 15587)
+++ CalendarServer/trunk/contrib/performance/loadtest/population.py	2016-05-12 19:28:46 UTC (rev 15588)
@@ -516,7 +516,10 @@
         # Get means for each type of method
         means = {}
         for method, results in self._perMethodTimes.items():
-            means[method] = mean([duration for success, duration in results if success])
+            try:
+                means[method] = mean([duration for success, duration in results if success])
+            except ZeroDivisionError:
+                pass # Ignore the case where all samples were unsuccessful?
 
         # Determine percentage differences with weighting
         differences = []

Modified: CalendarServer/trunk/contrib/performance/loadtest/profiles.py
===================================================================
--- CalendarServer/trunk/contrib/performance/loadtest/profiles.py	2016-05-11 16:03:38 UTC (rev 15587)
+++ CalendarServer/trunk/contrib/performance/loadtest/profiles.py	2016-05-12 19:28:46 UTC (rev 15588)
@@ -24,6 +24,7 @@
 import json
 import random
 import sys
+from urlparse import urljoin, urlparse
 from uuid import uuid4
 
 from caldavclientlibrary.protocol.caldav.definitions import caldavxml
@@ -260,6 +261,7 @@
             120 * 60
         ]),
         recurrenceDistribution=RecurrenceDistribution(False),
+        fileSizeDistribution=NormalDistribution(1024, 1),
     ):
         self.enabled = enabled
         self._sendInvitationDistribution = sendInvitationDistribution
@@ -269,6 +271,7 @@
         self._eventStartDistribution = eventStartDistribution
         self._eventDurationDistribution = eventDurationDistribution
         self._recurrenceDistribution = recurrenceDistribution
+        self._fileSizeDistribution = fileSizeDistribution
 
 
     def run(self):
@@ -333,7 +336,7 @@
             return succeed(None)
 
         # Find calendars which are eligible for invites
-        calendars = self._calendarsOfType(caldavxml.calendar, "VEVENT")
+        calendars = self._calendarsOfType(caldavxml.calendar, "VEVENT", justOwned=True)
 
         while calendars:
             # Pick one at random from which to try to create an event
@@ -368,8 +371,9 @@
                 except CannotAddAttendee:
                     continue
 
+            attachmentSize = int(self._fileSizeDistribution.sample())
             href = '%s%s.ics' % (calendar.url, uid)
-            d = self._client.addInvite(href, vcalendar)
+            d = self._client.addInvite(href, vcalendar, attachmentSize=attachmentSize)
             return self._newOperation("invite", d)
 
 
@@ -561,12 +565,17 @@
             for attachment in attachments:
                 attachmentHref = attachment.value()
                 managedId = attachment.parameterValue('MANAGED-ID')
+                attachmentHref = self._normalizeHref(attachmentHref)
+
                 self._reactor.callLater(
                     0, self._client.getAttachment, attachmentHref, managedId
                 )
 
+    def _normalizeHref(self, href):
+        return urljoin(self._client._managed_attachments_server_url, urlparse(href).path)
 
 
+
 class Eventer(ProfileBase):
     """
     A Calendar user who creates new events.
@@ -600,12 +609,14 @@
             120 * 60
         ]),
         recurrenceDistribution=RecurrenceDistribution(False),
+        fileSizeDistribution=NormalDistribution(1024, 1),
     ):
         self.enabled = enabled
         self._interval = interval
         self._eventStartDistribution = eventStartDistribution
         self._eventDurationDistribution = eventDurationDistribution
         self._recurrenceDistribution = recurrenceDistribution
+        self._fileSizeDistribution = fileSizeDistribution
 
 
     def run(self):
@@ -642,8 +653,9 @@
         if rrule is not None:
             vevent.addProperty(Property(None, None, None, pycalendar=rrule))
 
+        attachmentSize = int(self._fileSizeDistribution.sample())
         href = '%s%s.ics' % (calendar.url, uid)
-        d = self._client.addEvent(href, vcalendar)
+        d = self._client.addEvent(href, vcalendar, attachmentSize=attachmentSize)
         return self._newOperation("create", d)
 
 

Modified: 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	2016-05-11 16:03:38 UTC (rev 15587)
+++ CalendarServer/trunk/contrib/performance/loadtest/request-data/OS_X_10_11/poll_calendarhome_depth1_propfind.request	2016-05-12 19:28:46 UTC (rev 15588)
@@ -39,5 +39,6 @@
     <B:supported-calendar-component-sets xmlns:B="urn:ietf:params:xml:ns:caldav"/>
     <A:supported-report-set/>
     <A:sync-token/>
+    <B:managed-attachments-server-URL xmlns:B="urn:ietf:params:xml:ns:caldav"/>
   </A:prop>
 </A:propfind>

Modified: CalendarServer/trunk/contrib/performance/loadtest/test_ical.py
===================================================================
--- CalendarServer/trunk/contrib/performance/loadtest/test_ical.py	2016-05-11 16:03:38 UTC (rev 15587)
+++ CalendarServer/trunk/contrib/performance/loadtest/test_ical.py	2016-05-12 19:28:46 UTC (rev 15588)
@@ -1358,7 +1358,7 @@
             response = MemoryResponse(
                 ('HTTP', '1', '1'), CREATED, "Created", Headers({"etag": ["foo"]}),
                 StringProducer(""))
-            result.callback(response)
+            result.callback((response, ""))
         finished.addCallback(requested)
 
         return d
@@ -1399,7 +1399,7 @@
                 ('HTTP', '1', '1'), MULTI_STATUS, "MultiStatus", Headers({}),
                 StringProducer("<?xml version='1.0' encoding='UTF-8'?><multistatus xmlns='DAV:' />"))
 
-            returnValue(response)
+            returnValue((response, "<?xml version='1.0' encoding='UTF-8'?><multistatus xmlns='DAV:' />"))
 
         @inlineCallbacks
         def _testPost(*args, **kwargs):
@@ -1418,7 +1418,7 @@
                 ('HTTP', '1', '1'), OK, "OK", Headers({}),
                 StringProducer(""))
 
-            returnValue(response)
+            returnValue((response, ""))
 
         def _testPost02(*args, **kwargs):
             return _testPost(*args, attendee="ATTENDEE:mailto:user02 at example.com", **kwargs)
@@ -1445,7 +1445,7 @@
                 ('HTTP', '1', '1'), CREATED, "Created", Headers({}),
                 StringProducer(""))
 
-            returnValue(response)
+            returnValue((response, ""))
 
         def _testGet(*args, **kwargs):
             expectedResponseCode, method, url = args
@@ -1458,7 +1458,7 @@
                 ('HTTP', '1', '1'), OK, "OK", Headers({"etag": ["foo"]}),
                 StringProducer(EVENT_INVITE))
 
-            return succeed(response)
+            return succeed((response, EVENT_INVITE))
 
         requests = [_testReport, _testPost02, _testReport, _testPost03, _testPut, _testGet]
 
@@ -1499,7 +1499,7 @@
         response = MemoryResponse(
             ('HTTP', '1', '1'), NO_CONTENT, "No Content", None,
             StringProducer(""))
-        result.callback(response)
+        result.callback((response, ""))
         return d
 
 
@@ -1935,9 +1935,13 @@
         self.assertEqual((MULTI_STATUS, FORBIDDEN), expectedResponseCode)
 
         result.callback(
-            MemoryResponse(
-                ('HTTP', '1', '1'), MULTI_STATUS, "Multi-status", None,
-                StringProducer(self._CALENDAR_PROPFIND_RESPONSE_BODY)))
+            (
+                MemoryResponse(
+                    ('HTTP', '1', '1'), MULTI_STATUS, "Multi-status", None,
+                    StringProducer(self._CALENDAR_PROPFIND_RESPONSE_BODY)),
+                self._CALENDAR_PROPFIND_RESPONSE_BODY
+            )
+        )
 
         result, req = requests.pop(0)
         expectedResponseCode, method, url, _ignore_headers, _ignore_body = req
@@ -1949,9 +1953,13 @@
         del self.client._events["/something/anotherthing.ics"]
 
         result.callback(
-            MemoryResponse(
-                ('HTTP', '1', '1'), MULTI_STATUS, "Multi-status", None,
-                StringProducer(self._CALENDAR_REPORT_RESPONSE_BODY)))
+            (
+                MemoryResponse(
+                    ('HTTP', '1', '1'), MULTI_STATUS, "Multi-status", None,
+                    StringProducer(self._CALENDAR_REPORT_RESPONSE_BODY)),
+                self._CALENDAR_REPORT_RESPONSE_BODY
+            )
+        )
 
         # Verify that processing proceeded to the response after the one with a
         # 404 status.
@@ -1978,9 +1986,13 @@
         self.assertEqual((MULTI_STATUS, FORBIDDEN), expectedResponseCode)
 
         result.callback(
-            MemoryResponse(
-                ('HTTP', '1', '1'), MULTI_STATUS, "Multi-status", None,
-                StringProducer(self._CALENDAR_PROPFIND_RESPONSE_BODY)))
+            (
+                MemoryResponse(
+                    ('HTTP', '1', '1'), MULTI_STATUS, "Multi-status", None,
+                    StringProducer(self._CALENDAR_PROPFIND_RESPONSE_BODY)),
+                self._CALENDAR_PROPFIND_RESPONSE_BODY
+            )
+        )
 
         result, req = requests.pop(0)
         expectedResponseCode, method, url, _ignore_headers, _ignore_body = req
@@ -1989,9 +2001,13 @@
         self.assertEqual((MULTI_STATUS,), expectedResponseCode)
 
         result.callback(
-            MemoryResponse(
-                ('HTTP', '1', '1'), MULTI_STATUS, "Multi-status", None,
-                StringProducer(self._CALENDAR_REPORT_RESPONSE_BODY_1)))
+            (
+                MemoryResponse(
+                    ('HTTP', '1', '1'), MULTI_STATUS, "Multi-status", None,
+                    StringProducer(self._CALENDAR_REPORT_RESPONSE_BODY_1)),
+                self._CALENDAR_REPORT_RESPONSE_BODY_1
+            )
+        )
 
         self.assertTrue(self.client._events['/something/anotherthing.ics'].etag is not None)
         self.assertTrue(self.client._events['/something/else.ics'].etag is None)
@@ -2003,9 +2019,13 @@
         self.assertEqual((MULTI_STATUS,), expectedResponseCode)
 
         result.callback(
-            MemoryResponse(
-                ('HTTP', '1', '1'), MULTI_STATUS, "Multi-status", None,
-                StringProducer(self._CALENDAR_REPORT_RESPONSE_BODY_2)))
+            (
+                MemoryResponse(
+                    ('HTTP', '1', '1'), MULTI_STATUS, "Multi-status", None,
+                    StringProducer(self._CALENDAR_REPORT_RESPONSE_BODY_2)),
+                self._CALENDAR_REPORT_RESPONSE_BODY_2
+            )
+        )
 
         self.assertTrue(self.client._events['/something/anotherthing.ics'].etag is not None)
         self.assertTrue(self.client._events['/something/else.ics'].etag is not None)
@@ -2077,7 +2097,7 @@
             response = MemoryResponse(
                 ('HTTP', '1', '1'), OK, "Ok", Headers({}),
                 StringProducer(""))
-            result.callback(response)
+            result.callback((response, ""))
         finished.addCallback(requested)
 
         return d

Modified: CalendarServer/trunk/contrib/performance/loadtest/test_profiles.py
===================================================================
--- CalendarServer/trunk/contrib/performance/loadtest/test_profiles.py	2016-05-11 16:03:38 UTC (rev 15587)
+++ CalendarServer/trunk/contrib/performance/loadtest/test_profiles.py	2016-05-12 19:28:46 UTC (rev 15588)
@@ -31,7 +31,7 @@
 
 from twistedcaldav.ical import Component, Property
 
-from contrib.performance.loadtest.profiles import Eventer, Inviter, Accepter, OperationLogger, AlarmAcknowledger
+from contrib.performance.loadtest.profiles import Eventer, Inviter, Accepter, OperationLogger, AlarmAcknowledger, AttachmentDownloader
 from contrib.performance.loadtest.population import Populator, CalendarClientSimulator
 from contrib.performance.loadtest.ical import IncorrectResponseCode, Calendar, Event, BaseClient
 from contrib.performance.loadtest.sim import _DirectoryRecord
@@ -258,13 +258,13 @@
         return path
 
 
-    def addEvent(self, href, vevent):
+    def addEvent(self, href, vevent, attachmentSize=0):
         self._events[href] = Event(self.serializePath, href, None, vevent)
         return succeed(None)
 
 
-    def addInvite(self, href, vevent):
-        return self.addEvent(href, vevent)
+    def addInvite(self, href, vevent, attachmentSize=0):
+        return self.addEvent(href, vevent, attachmentSize=attachmentSize)
 
 
     def deleteEvent(self, href,):
@@ -291,11 +291,14 @@
 
     def changeEventAttendee(self, href, old, new):
         if href in self.rescheduled:
-            return fail(IncorrectResponseCode(
-                NO_CONTENT,
-                Response(
-                    ('HTTP', 1, 1), PRECONDITION_FAILED,
-                    'Precondition Failed', None, None))
+            return fail(
+                IncorrectResponseCode(
+                    NO_CONTENT,
+                    Response(
+                        ('HTTP', 1, 1), PRECONDITION_FAILED,
+                        'Precondition Failed', None, None),
+                    None
+                ),
             )
 
         vevent = self._events[href].component
@@ -851,7 +854,7 @@
             NO_CONTENT,
             Response(
                 ('HTTP', 1, 1), PRECONDITION_FAILED,
-                'Precondition Failed', None, None))
+                'Precondition Failed', None, None), None)
         )
         accepter = Accepter(clock, self.sim, client, userNumber)
         accepter.eventChanged(inboxEvent.url)
@@ -1034,7 +1037,25 @@
         # XXX Vary the event period/interval and the uid
 
 
+class AttachmentDownloaderTests(TestCase):
+    """
+    Tests for L{AttachmentDownloader}.
+    """
 
+    def setUp(self):
+        self.sim = CalendarClientSimulator(
+            AnyUser(), Populator(None), None, None, None, None, None, None)
+
+    def test_normalize(self):
+        client = StubClient(1, self.mktemp())
+        client._managed_attachments_server_url = "https://thisserver:8443"
+        downloader = AttachmentDownloader(Clock(), self.sim, client, None)
+        self.assertEquals(
+            downloader._normalizeHref("http://otherserver/attachment/url"),
+            "https://thisserver:8443/attachment/url"
+        )
+
+
 class OperationLoggerTests(TestCase):
     """
     Tests for L{OperationLogger}.

Modified: CalendarServer/trunk/requirements-dev.txt
===================================================================
--- CalendarServer/trunk/requirements-dev.txt	2016-05-11 16:03:38 UTC (rev 15587)
+++ CalendarServer/trunk/requirements-dev.txt	2016-05-12 19:28:46 UTC (rev 15588)
@@ -4,5 +4,5 @@
 mockldap
 q
 tl.eggdeps
---editable svn+http://svn.calendarserver.org/repository/calendarserver/CalDAVClientLibrary/trunk@15425#egg=CalDAVClientLibrary
+--editable svn+http://svn.calendarserver.org/repository/calendarserver/CalDAVClientLibrary/trunk@15578#egg=CalDAVClientLibrary
 --editable svn+http://svn.calendarserver.org/repository/calendarserver/CalDAVTester/trunk@15551#egg=CalDAVTester
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://lists.macosforge.org/pipermail/calendarserver-changes/attachments/20160512/19f355bb/attachment-0001.html>


More information about the calendarserver-changes mailing list