<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head><meta http-equiv="content-type" content="text/html; charset=utf-8" />
<title>[15588] CalendarServer/trunk</title>
</head>
<body>

<style type="text/css"><!--
#msg dl.meta { border: 1px #006 solid; background: #369; padding: 6px; color: #fff; }
#msg dl.meta dt { float: left; width: 6em; font-weight: bold; }
#msg dt:after { content:':';}
#msg dl, #msg dt, #msg ul, #msg li, #header, #footer, #logmsg { font-family: verdana,arial,helvetica,sans-serif; font-size: 10pt;  }
#msg dl a { font-weight: bold}
#msg dl a:link    { color:#fc3; }
#msg dl a:active  { color:#ff0; }
#msg dl a:visited { color:#cc6; }
h3 { font-family: verdana,arial,helvetica,sans-serif; font-size: 10pt; font-weight: bold; }
#msg pre { overflow: auto; background: #ffc; border: 1px #fa0 solid; padding: 6px; }
#logmsg { background: #ffc; border: 1px #fa0 solid; padding: 1em 1em 0 1em; }
#logmsg p, #logmsg pre, #logmsg blockquote { margin: 0 0 1em 0; }
#logmsg p, #logmsg li, #logmsg dt, #logmsg dd { line-height: 14pt; }
#logmsg h1, #logmsg h2, #logmsg h3, #logmsg h4, #logmsg h5, #logmsg h6 { margin: .5em 0; }
#logmsg h1:first-child, #logmsg h2:first-child, #logmsg h3:first-child, #logmsg h4:first-child, #logmsg h5:first-child, #logmsg h6:first-child { margin-top: 0; }
#logmsg ul, #logmsg ol { padding: 0; list-style-position: inside; margin: 0 0 0 1em; }
#logmsg ul { text-indent: -1em; padding-left: 1em; }#logmsg ol { text-indent: -1.5em; padding-left: 1.5em; }
#logmsg > ul, #logmsg > ol { margin: 0 0 1em 0; }
#logmsg pre { background: #eee; padding: 1em; }
#logmsg blockquote { border: 1px solid #fa0; border-left-width: 10px; padding: 1em 1em 0 1em; background: white;}
#logmsg dl { margin: 0; }
#logmsg dt { font-weight: bold; }
#logmsg dd { margin: 0; padding: 0 0 0.5em 0; }
#logmsg dd:before { content:'\00bb';}
#logmsg table { border-spacing: 0px; border-collapse: collapse; border-top: 4px solid #fa0; border-bottom: 1px solid #fa0; background: #fff; }
#logmsg table th { text-align: left; font-weight: normal; padding: 0.2em 0.5em; border-top: 1px dotted #fa0; }
#logmsg table td { text-align: right; border-top: 1px dotted #fa0; padding: 0.2em 0.5em; }
#logmsg table thead th { text-align: center; border-bottom: 1px solid #fa0; }
#logmsg table th.Corner { text-align: left; }
#logmsg hr { border: none 0; border-top: 2px dashed #fa0; height: 1px; }
#header, #footer { color: #fff; background: #636; border: 1px #300 solid; padding: 6px; }
#patch { width: 100%; }
#patch h4 {font-family: verdana,arial,helvetica,sans-serif;font-size:10pt;padding:8px;background:#369;color:#fff;margin:0;}
#patch .propset h4, #patch .binary h4 {margin:0;}
#patch pre {padding:0;line-height:1.2em;margin:0;}
#patch .diff {width:100%;background:#eee;padding: 0 0 10px 0;overflow:auto;}
#patch .propset .diff, #patch .binary .diff  {padding:10px 0;}
#patch span {display:block;padding:0 10px;}
#patch .modfile, #patch .addfile, #patch .delfile, #patch .propset, #patch .binary, #patch .copfile {border:1px solid #ccc;margin:10px 0;}
#patch ins {background:#dfd;text-decoration:none;display:block;padding:0 10px;}
#patch del {background:#fdd;text-decoration:none;display:block;padding:0 10px;}
#patch .lines, .info {color:#888;background:#fff;}
--></style>
<div id="msg">
<dl class="meta">
<dt>Revision</dt> <dd><a href="http://trac.calendarserver.org//changeset/15588">15588</a></dd>
<dt>Author</dt> <dd>sagen@apple.com</dd>
<dt>Date</dt> <dd>2016-05-12 12:28:46 -0700 (Thu, 12 May 2016)</dd>
</dl>

<h3>Log Message</h3>
<pre>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 &quot;home&quot; server (or the server specified in the managed-attachments-server-URL property.</pre>

<h3>Modified Paths</h3>
<ul>
<li><a href="#CalendarServertrunkcontribperformanceloadtestclientsplist">CalendarServer/trunk/contrib/performance/loadtest/clients.plist</a></li>
<li><a href="#CalendarServertrunkcontribperformanceloadtestconfigplist">CalendarServer/trunk/contrib/performance/loadtest/config.plist</a></li>
<li><a href="#CalendarServertrunkcontribperformanceloadtesticalpy">CalendarServer/trunk/contrib/performance/loadtest/ical.py</a></li>
<li><a href="#CalendarServertrunkcontribperformanceloadtestpopulationpy">CalendarServer/trunk/contrib/performance/loadtest/population.py</a></li>
<li><a href="#CalendarServertrunkcontribperformanceloadtestprofilespy">CalendarServer/trunk/contrib/performance/loadtest/profiles.py</a></li>
<li><a href="#CalendarServertrunkcontribperformanceloadtestrequestdataOS_X_10_11poll_calendarhome_depth1_propfindrequest">CalendarServer/trunk/contrib/performance/loadtest/request-data/OS_X_10_11/poll_calendarhome_depth1_propfind.request</a></li>
<li><a href="#CalendarServertrunkcontribperformanceloadtesttest_icalpy">CalendarServer/trunk/contrib/performance/loadtest/test_ical.py</a></li>
<li><a href="#CalendarServertrunkcontribperformanceloadtesttest_profilespy">CalendarServer/trunk/contrib/performance/loadtest/test_profiles.py</a></li>
<li><a href="#CalendarServertrunkrequirementsdevtxt">CalendarServer/trunk/requirements-dev.txt</a></li>
</ul>

</div>
<div id="patch">
<h3>Diff</h3>
<a id="CalendarServertrunkcontribperformanceloadtestclientsplist"></a>
<div class="modfile"><h4>Modified: CalendarServer/trunk/contrib/performance/loadtest/clients.plist (15587 => 15588)</h4>
<pre class="diff"><span>
<span class="info">--- 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)
</span><span class="lines">@@ -38,7 +38,7 @@
</span><span class="cx">                                 &lt;dict&gt;
</span><span class="cx">                                         &lt;!-- Name that appears in logs. --&gt;
</span><span class="cx">                                         &lt;key&gt;title&lt;/key&gt;
</span><del>-                                        &lt;string&gt;10.11&lt;/string&gt;
</del><ins>+                                        &lt;string&gt;10.11a&lt;/string&gt;
</ins><span class="cx"> 
</span><span class="cx">                                         &lt;!-- OS_X_10_11 can poll the calendar home at some interval. This is
</span><span class="cx">                                                 in seconds. --&gt;
</span><span class="lines">@@ -163,6 +163,24 @@
</span><span class="cx">                                                                         &lt;/dict&gt;
</span><span class="cx">                                                                 &lt;/dict&gt;
</span><span class="cx">                                                         &lt;/dict&gt;
</span><ins>+
+                                                        &lt;!-- Define the attachment size distribution. --&gt;
+                                                        &lt;key&gt;fileSizeDistribution&lt;/key&gt;
+                                                        &lt;dict&gt;
+                                                                &lt;key&gt;type&lt;/key&gt;
+                                                                &lt;string&gt;contrib.performance.stats.NormalDistribution&lt;/string&gt;
+                                                                &lt;key&gt;params&lt;/key&gt;
+                                                                &lt;dict&gt;
+                                                                        &lt;!-- mu gives the mean of the normal distribution (in seconds). --&gt;
+                                                                        &lt;key&gt;mu&lt;/key&gt;
+                                                                        &lt;integer&gt;500000&lt;/integer&gt;
+
+                                                                        &lt;!-- and sigma gives its standard deviation. --&gt;
+                                                                        &lt;key&gt;sigma&lt;/key&gt;
+                                                                        &lt;integer&gt;100000&lt;/integer&gt;
+                                                                &lt;/dict&gt;
+                                                        &lt;/dict&gt;
+
</ins><span class="cx">                                                 &lt;/dict&gt;
</span><span class="cx">                                         &lt;/dict&gt;
</span><span class="cx"> 
</span><span class="lines">@@ -343,7 +361,7 @@
</span><span class="cx">                                                 &lt;key&gt;params&lt;/key&gt;
</span><span class="cx">                                                 &lt;dict&gt;
</span><span class="cx">                                                         &lt;key&gt;enabled&lt;/key&gt;
</span><del>-                                                        &lt;true/&gt;
</del><ins>+                                                        &lt;false/&gt;
</ins><span class="cx"> 
</span><span class="cx">                                                         &lt;!-- Define the interval (in seconds) at which this profile will use
</span><span class="cx">                                                                 its client to create a new event. --&gt;
</span><span class="lines">@@ -578,6 +596,24 @@
</span><span class="cx">                                                                         &lt;/dict&gt;
</span><span class="cx">                                                                 &lt;/dict&gt;
</span><span class="cx">                                                         &lt;/dict&gt;
</span><ins>+
+                                                        &lt;!-- Define the attachment size distribution. --&gt;
+                                                        &lt;key&gt;fileSizeDistribution&lt;/key&gt;
+                                                        &lt;dict&gt;
+                                                                &lt;key&gt;type&lt;/key&gt;
+                                                                &lt;string&gt;contrib.performance.stats.NormalDistribution&lt;/string&gt;
+                                                                &lt;key&gt;params&lt;/key&gt;
+                                                                &lt;dict&gt;
+                                                                        &lt;!-- mu gives the mean of the normal distribution (in seconds). --&gt;
+                                                                        &lt;key&gt;mu&lt;/key&gt;
+                                                                        &lt;integer&gt;500000&lt;/integer&gt;
+
+                                                                        &lt;!-- and sigma gives its standard deviation. --&gt;
+                                                                        &lt;key&gt;sigma&lt;/key&gt;
+                                                                        &lt;integer&gt;100000&lt;/integer&gt;
+                                                                &lt;/dict&gt;
+                                                        &lt;/dict&gt;
+
</ins><span class="cx">                                                 &lt;/dict&gt;
</span><span class="cx">                                         &lt;/dict&gt;
</span><span class="cx"> 
</span><span class="lines">@@ -712,6 +748,58 @@
</span><span class="cx">                                 &lt;key&gt;weight&lt;/key&gt;
</span><span class="cx">                                 &lt;integer&gt;1&lt;/integer&gt;
</span><span class="cx">                         &lt;/dict&gt;
</span><ins>+
+                        &lt;dict&gt;
+
+                                &lt;!-- Here is a more passive OS X client simulator. --&gt;
+                                &lt;key&gt;software&lt;/key&gt;
+                                &lt;string&gt;contrib.performance.loadtest.ical.OS_X_10_11&lt;/string&gt;
+
+                                &lt;!-- Arguments to use to initialize the OS_X_10_11 instance. --&gt;
+                                &lt;key&gt;params&lt;/key&gt;
+                                &lt;dict&gt;
+                                        &lt;!-- Name that appears in logs. --&gt;
+                                        &lt;key&gt;title&lt;/key&gt;
+                                        &lt;string&gt;10.11b&lt;/string&gt;
+
+                                        &lt;!-- OS_X_10_11 can poll the calendar home at some interval. This is
+                                                in seconds. --&gt;
+                                        &lt;key&gt;calendarHomePollInterval&lt;/key&gt;
+                                        &lt;integer&gt;30&lt;/integer&gt;
+
+                                        &lt;!-- 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. --&gt;
+                                        &lt;key&gt;supportPush&lt;/key&gt;
+                                        &lt;true/&gt;
+
+                                        &lt;key&gt;supportAmpPush&lt;/key&gt;
+                                        &lt;true/&gt;
+                                &lt;/dict&gt;
+
+                                &lt;!-- The profiles define certain types of user behavior on top of the
+                                        client software being simulated. --&gt;
+                                &lt;key&gt;profiles&lt;/key&gt;
+                                &lt;array&gt;
+                                        &lt;!-- This profile downloads attachments when an event changes. --&gt;
+                                        &lt;dict&gt;
+                                                &lt;key&gt;class&lt;/key&gt;
+                                                &lt;string&gt;contrib.performance.loadtest.profiles.AttachmentDownloader&lt;/string&gt;
+
+                                                &lt;key&gt;params&lt;/key&gt;
+                                                &lt;dict&gt;
+                                                        &lt;key&gt;enabled&lt;/key&gt;
+                                                        &lt;true/&gt;
+                                                &lt;/dict&gt;
+                                        &lt;/dict&gt;
+                                &lt;/array&gt;
+                                &lt;!-- Determine the frequency at which this client configuration will
+                                        appear in the clients which are created by the load tester. --&gt;
+                                &lt;key&gt;weight&lt;/key&gt;
+                                &lt;integer&gt;1&lt;/integer&gt;
+                        &lt;/dict&gt;
</ins><span class="cx">                 &lt;/array&gt;
</span><span class="cx">         &lt;/dict&gt;
</span><span class="cx"> &lt;/plist&gt;
</span></span></pre></div>
<a id="CalendarServertrunkcontribperformanceloadtestconfigplist"></a>
<div class="modfile"><h4>Modified: CalendarServer/trunk/contrib/performance/loadtest/config.plist (15587 => 15588)</h4>
<pre class="diff"><span>
<span class="info">--- 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)
</span><span class="lines">@@ -157,7 +157,7 @@
</span><span class="cx">                                 &lt;!-- Number of clients each user is assigned to. --&gt;
</span><span class="cx">                                 &lt;!-- Set weight of clients to 1 if this is &gt; 1. Number of clients must match this value if &gt; 1. --&gt;
</span><span class="cx">                                 &lt;key&gt;clientsPerUser&lt;/key&gt;
</span><del>-                                &lt;integer&gt;1&lt;/integer&gt;
</del><ins>+                                &lt;integer&gt;2&lt;/integer&gt;
</ins><span class="cx">                         &lt;/dict&gt;
</span><span class="cx"> 
</span><span class="cx">                 &lt;/dict&gt;
</span><span class="lines">@@ -168,7 +168,8 @@
</span><span class="cx">                         &lt;!-- ReportStatistics generates an end-of-run summary of the HTTP requests
</span><span class="cx">                                 made, their timings, and their results. --&gt;
</span><span class="cx">                         &lt;dict&gt;
</span><del>-                                &lt;key&gt;type&lt;/key&gt;
</del><ins>+                        &lt;key&gt;type&lt;/key&gt;
+
</ins><span class="cx">                                 &lt;string&gt;contrib.performance.loadtest.population.ReportStatistics&lt;/string&gt;
</span><span class="cx">                                 &lt;key&gt;params&lt;/key&gt;
</span><span class="cx">                                 &lt;dict&gt;
</span></span></pre></div>
<a id="CalendarServertrunkcontribperformanceloadtesticalpy"></a>
<div class="modfile"><h4>Modified: CalendarServer/trunk/contrib/performance/loadtest/ical.py (15587 => 15588)</h4>
<pre class="diff"><span>
<span class="info">--- 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)
</span><span class="lines">@@ -85,10 +85,14 @@
</span><span class="cx"> 
</span><span class="cx">     @ivar response: The response which was received
</span><span class="cx">     @type response: L{twisted.web.client.Response}
</span><ins>+
+    @ivar responseBody: The body of the received response
+    @type responseBody: C{str}
</ins><span class="cx">     &quot;&quot;&quot;
</span><del>-    def __init__(self, expected, response):
</del><ins>+    def __init__(self, expected, response, responseBody):
</ins><span class="cx">         self.expected = expected
</span><span class="cx">         self.response = response
</span><ins>+        self.responseBody = responseBody
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="lines">@@ -447,6 +451,8 @@
</span><span class="cx"> 
</span><span class="cx">     _client_type = &quot;Generic&quot;
</span><span class="cx"> 
</span><ins>+    _managed_attachments_server_url = None
+
</ins><span class="cx">     USER_AGENT = None   # Override this for specific clients
</span><span class="cx"> 
</span><span class="cx">     # The default interval, used if none is specified in external
</span><span class="lines">@@ -618,9 +624,8 @@
</span><span class="cx">         before = self.reactor.seconds()
</span><span class="cx">         response = yield self.agent.request(method, url, headers, body)
</span><span class="cx"> 
</span><del>-        # XXX This is time to receive response headers, not time
-        # to receive full response.  Should measure the latter, if
-        # not both.
</del><ins>+        responseBody = yield readBody(response)
+
</ins><span class="cx">         after = self.reactor.seconds()
</span><span class="cx"> 
</span><span class="cx">         success = response.code in expectedResponseCodes
</span><span class="lines">@@ -631,6 +636,7 @@
</span><span class="cx">             method=method_label if method_label else method,
</span><span class="cx">             headers=headers,
</span><span class="cx">             body=body,
</span><ins>+            responseBody=responseBody,
</ins><span class="cx">             code=response.code,
</span><span class="cx">             user=self.record.uid,
</span><span class="cx">             client_type=self.title,
</span><span class="lines">@@ -640,22 +646,30 @@
</span><span class="cx">         )
</span><span class="cx"> 
</span><span class="cx">         if success:
</span><del>-            returnValue(response)
</del><ins>+            returnValue((response, responseBody))
</ins><span class="cx"> 
</span><del>-        raise IncorrectResponseCode(expectedResponseCodes, response)
</del><ins>+        raise IncorrectResponseCode(expectedResponseCodes, response, responseBody)
</ins><span class="cx"> 
</span><span class="cx"> 
</span><del>-    def _parseMultiStatus(self, response, otherTokens=False):
</del><ins>+    def _parseMultiStatus(self, responseBody, otherTokens=False):
</ins><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         Parse a &lt;multistatus&gt; - might need to return other top-level elements
</span><span class="cx">         in the response - e.g. DAV:sync-token
</span><span class="cx">         I{PROPFIND} request for the principal URL.
</span><span class="cx"> 
</span><del>-        @type response: C{str}
</del><ins>+        @type responseBody: C{str}
</ins><span class="cx">         @rtype: C{cls}
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         parser = PropFindParser()
</span><del>-        parser.parseData(response)
</del><ins>+        try:
+            parser.parseData(responseBody)
+        except:
+            print(&quot;=&quot; * 80)
+            print(&quot;COULD NOT PARSE RESPONSE:&quot;)
+            print(responseBody)
+            print(&quot;=&quot; * 80)
+            raise
+
</ins><span class="cx">         if otherTokens:
</span><span class="cx">             return (parser.getResults(), parser.getOthers(),)
</span><span class="cx">         else:
</span><span class="lines">@@ -674,7 +688,7 @@
</span><span class="cx">         hdrs = Headers({'content-type': ['text/xml']})
</span><span class="cx">         if depth is not None:
</span><span class="cx">             hdrs.addRawHeader('depth', depth)
</span><del>-        response = yield self._request(
</del><ins>+        response, responseBody = yield self._request(
</ins><span class="cx">             allowedStatus,
</span><span class="cx">             'PROPFIND',
</span><span class="cx">             self.server[&quot;uri&quot;] + url.encode('utf-8'),
</span><span class="lines">@@ -683,8 +697,7 @@
</span><span class="cx">             method_label=method_label,
</span><span class="cx">         )
</span><span class="cx"> 
</span><del>-        body = yield readBody(response)
-        result = self._parseMultiStatus(body) if response.code == MULTI_STATUS else None
</del><ins>+        result = self._parseMultiStatus(responseBody) if response.code == MULTI_STATUS else None
</ins><span class="cx"> 
</span><span class="cx">         returnValue((response, result,))
</span><span class="cx"> 
</span><span class="lines">@@ -695,7 +708,7 @@
</span><span class="cx">         Issue a PROPPATCH on the chosen URL
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         hdrs = Headers({'content-type': ['text/xml']})
</span><del>-        response = yield self._request(
</del><ins>+        response, responseBody = yield self._request(
</ins><span class="cx">             (OK, MULTI_STATUS,),
</span><span class="cx">             'PROPPATCH',
</span><span class="cx">             self.server[&quot;uri&quot;] + url.encode('utf-8'),
</span><span class="lines">@@ -704,8 +717,7 @@
</span><span class="cx">             method_label=method_label,
</span><span class="cx">         )
</span><span class="cx">         if response.code == MULTI_STATUS:
</span><del>-            body = yield readBody(response)
-            result = self._parseMultiStatus(body)
</del><ins>+            result = self._parseMultiStatus(responseBody)
</ins><span class="cx">             returnValue(result)
</span><span class="cx">         else:
</span><span class="cx">             returnValue(None)
</span><span class="lines">@@ -716,15 +728,14 @@
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         Issue a GET on the chosen URL
</span><span class="cx">         &quot;&quot;&quot;
</span><del>-        response = yield self._request(
</del><ins>+        response, responseBody = yield self._request(
</ins><span class="cx">             allowedStatus,
</span><span class="cx">             'GET',
</span><span class="cx">             url,
</span><span class="cx">             method_label=method_label,
</span><span class="cx">         )
</span><span class="cx"> 
</span><del>-        body = yield readBody(response)
-        returnValue(body)
</del><ins>+        returnValue(responseBody)
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     @inlineCallbacks
</span><span class="lines">@@ -735,7 +746,7 @@
</span><span class="cx">         hdrs = Headers({'content-type': ['text/xml']})
</span><span class="cx">         if depth is not None:
</span><span class="cx">             hdrs.addRawHeader('depth', depth)
</span><del>-        response = yield self._request(
</del><ins>+        response, responseBody = yield self._request(
</ins><span class="cx">             allowedStatus,
</span><span class="cx">             'REPORT',
</span><span class="cx">             self.server[&quot;uri&quot;] + url.encode('utf-8'),
</span><span class="lines">@@ -744,8 +755,7 @@
</span><span class="cx">             method_label=method_label,
</span><span class="cx">         )
</span><span class="cx"> 
</span><del>-        body = yield readBody(response)
-        result = self._parseMultiStatus(body, otherTokens) if response.code == MULTI_STATUS else None
</del><ins>+        result = self._parseMultiStatus(responseBody, otherTokens) if response.code == MULTI_STATUS else None
</ins><span class="cx"> 
</span><span class="cx">         returnValue(result)
</span><span class="cx"> 
</span><span class="lines">@@ -890,8 +900,20 @@
</span><span class="cx"> 
</span><span class="cx">             if href == calendarHome:
</span><span class="cx">                 text = results[href].getTextProperties()
</span><ins>+                hrefs = results[href].getHrefProperties()
</ins><span class="cx"> 
</span><ins>+                # Extract managed attachments url
+                self._managed_attachments_server_url = None
</ins><span class="cx">                 try:
</span><ins>+                    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:
</ins><span class="cx">                     pushkey = text[csxml.pushkey]
</span><span class="cx">                 except KeyError:
</span><span class="cx">                     pass
</span><span class="lines">@@ -929,10 +951,9 @@
</span><span class="cx">             if isCalendar:
</span><span class="cx">                 textProps = results[href].getTextProperties()
</span><span class="cx">                 componentTypes = set()
</span><del>-                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(&quot;name&quot;).upper())
</del><ins>+                if caldavxml.supported_calendar_component_set in nodes:
+                    for comp in nodes[caldavxml.supported_calendar_component_set]:
+                        componentTypes.add(comp.get(&quot;name&quot;).upper())
</ins><span class="cx"> 
</span><span class="cx">                 calendars.append(Calendar(
</span><span class="cx">                     resourceType,
</span><span class="lines">@@ -1022,7 +1043,7 @@
</span><span class="cx">                     method_label=&quot;REPORT{sync}&quot; if calendar.changeToken else &quot;REPORT{sync-init}&quot;,
</span><span class="cx">                 )
</span><span class="cx">             else:
</span><del>-                raise IncorrectResponseCode((MULTI_STATUS,), None)
</del><ins>+                raise IncorrectResponseCode((MULTI_STATUS,), None, None)
</ins><span class="cx"> 
</span><span class="cx">         result, others = result
</span><span class="cx"> 
</span><span class="lines">@@ -1208,7 +1229,7 @@
</span><span class="cx">                     method_label=&quot;REPORT{sync}&quot; if oldToken else &quot;REPORT{sync-init}&quot;,
</span><span class="cx">                 )
</span><span class="cx">             else:
</span><del>-                raise IncorrectResponseCode((MULTI_STATUS,), None)
</del><ins>+                raise IncorrectResponseCode((MULTI_STATUS,), None, None)
</ins><span class="cx"> 
</span><span class="cx">         result, _ignore_others = result
</span><span class="cx"> 
</span><span class="lines">@@ -1229,24 +1250,24 @@
</span><span class="cx"> 
</span><span class="cx">             if result[responseHref].getStatus() / 100 == 2:
</span><span class="cx">                 # Get the notification
</span><del>-                response = yield self._request(
-                    OK,
</del><ins>+                response, responseBody = yield self._request(
+                    (OK, NOT_FOUND),
</ins><span class="cx">                     'GET',
</span><span class="cx">                     self.server[&quot;uri&quot;] + responseHref.encode('utf-8'),
</span><span class="cx">                     method_label=&quot;GET{notification}&quot;,
</span><span class="cx">                 )
</span><del>-                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
</del><ins>+                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
+                                        )
</ins><span class="cx">                                     )
</span><del>-                                )
</del><span class="cx"> 
</span><span class="cx">         # Accept the invites
</span><span class="cx">         for notification in inviteNotifications:
</span><span class="lines">@@ -1287,7 +1308,7 @@
</span><span class="cx"> 
</span><span class="cx">         # Delete all the notification resources
</span><span class="cx">         for responseHref in toDelete:
</span><del>-            response = yield self._request(
</del><ins>+            response, responseBody = yield self._request(
</ins><span class="cx">                 (NO_CONTENT, NOT_FOUND),
</span><span class="cx">                 'DELETE',
</span><span class="cx">                 self.server[&quot;uri&quot;] + responseHref.encode('utf-8'),
</span><span class="lines">@@ -1497,8 +1518,8 @@
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     def _receivedPush(self, inboundID, dataChangedTimestamp, priority=5):
</span><del>-        for href, id in self.ampPushKeys.iteritems():
-            if inboundID == id:
</del><ins>+        for href, myId in self.ampPushKeys.iteritems():
+            if inboundID == myId:
</ins><span class="cx">                 self._checkCalendarsForEvents(href, push=True)
</span><span class="cx">                 break
</span><span class="cx">         else:
</span><span class="lines">@@ -1708,7 +1729,7 @@
</span><span class="cx">             headers.removeHeader('if-match')
</span><span class="cx"> 
</span><span class="cx">         # At last, upload the new event definition
</span><del>-        response = yield self._request(
</del><ins>+        response, responseBody = yield self._request(
</ins><span class="cx">             (NO_CONTENT, PRECONDITION_FAILED,),
</span><span class="cx">             'PUT',
</span><span class="cx">             self.server[&quot;uri&quot;] + href.encode('utf-8'),
</span><span class="lines">@@ -1803,7 +1824,7 @@
</span><span class="cx">         if len(attendees) &gt; 75:
</span><span class="cx">             label_suffix = &quot;huge&quot;
</span><span class="cx"> 
</span><del>-        response = yield self._request(
</del><ins>+        response, responseBody = yield self._request(
</ins><span class="cx">             okCodes,
</span><span class="cx">             'PUT',
</span><span class="cx">             self.server[&quot;uri&quot;] + href.encode('utf-8'),
</span><span class="lines">@@ -1825,7 +1846,7 @@
</span><span class="cx"> 
</span><span class="cx">         self._removeEvent(href)
</span><span class="cx"> 
</span><del>-        response = yield self._request(
</del><ins>+        response, responseBody = yield self._request(
</ins><span class="cx">             (NO_CONTENT, NOT_FOUND),
</span><span class="cx">             'DELETE',
</span><span class="cx">             self.server[&quot;uri&quot;] + href.encode('utf-8'),
</span><span class="lines">@@ -1835,7 +1856,7 @@
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     @inlineCallbacks
</span><del>-    def addEvent(self, href, component, invite=False):
</del><ins>+    def addEvent(self, href, component, invite=False, attachmentSize=0):
</ins><span class="cx">         headers = Headers({
</span><span class="cx">             'content-type': ['text/calendar'],
</span><span class="cx">         })
</span><span class="lines">@@ -1849,7 +1870,7 @@
</span><span class="cx">         if len(attendees) &gt; 75:
</span><span class="cx">             label_suffix = &quot;huge&quot;
</span><span class="cx"> 
</span><del>-        response = yield self._request(
</del><ins>+        response, responseBody = yield self._request(
</ins><span class="cx">             CREATED,
</span><span class="cx">             'PUT',
</span><span class="cx">             self.server[&quot;uri&quot;] + href.encode('utf-8'),
</span><span class="lines">@@ -1862,9 +1883,14 @@
</span><span class="cx">         if not response.headers.hasHeader(&quot;etag&quot;):
</span><span class="cx">             response = yield self.updateEvent(href)
</span><span class="cx"> 
</span><ins>+        # Add optional attachment
+        if attachmentSize:
+            yield self.postAttachment(href, 'x' * attachmentSize)
</ins><span class="cx"> 
</span><ins>+
+
</ins><span class="cx">     @inlineCallbacks
</span><del>-    def addInvite(self, href, component):
</del><ins>+    def addInvite(self, href, component, attachmentSize=0):
</ins><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         Add an event that is an invite - i.e., has attendees. We will do attendee lookups and freebusy
</span><span class="cx">         checks on each attendee to simulate what happens when an organizer creates a new invite.
</span><span class="lines">@@ -1878,7 +1904,7 @@
</span><span class="cx">             yield self._attendeeAutoComplete(component, attendee)
</span><span class="cx"> 
</span><span class="cx">         # Now do a normal PUT
</span><del>-        yield self.addEvent(href, component, invite=True)
</del><ins>+        yield self.addEvent(href, component, invite=True, attachmentSize=attachmentSize)
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     @inlineCallbacks
</span><span class="lines">@@ -1894,7 +1920,7 @@
</span><span class="cx">             headers.removeHeader('if-match')
</span><span class="cx"> 
</span><span class="cx">         # At last, upload the new event definition
</span><del>-        response = yield self._request(
</del><ins>+        response, responseBody = yield self._request(
</ins><span class="cx">             (NO_CONTENT, PRECONDITION_FAILED,),
</span><span class="cx">             'PUT',
</span><span class="cx">             self.server[&quot;uri&quot;] + href.encode('utf-8'),
</span><span class="lines">@@ -1920,7 +1946,7 @@
</span><span class="cx"> 
</span><span class="cx">     @inlineCallbacks
</span><span class="cx">     def updateEvent(self, href):
</span><del>-        response = yield self._request(
</del><ins>+        response, responseBody = yield self._request(
</ins><span class="cx">             OK,
</span><span class="cx">             'GET',
</span><span class="cx">             self.server[&quot;uri&quot;] + href.encode('utf-8'),
</span><span class="lines">@@ -1929,8 +1955,7 @@
</span><span class="cx">         headers = response.headers
</span><span class="cx">         etag = headers.getRawHeaders('etag')[0]
</span><span class="cx">         scheduleTag = headers.getRawHeaders('schedule-tag', [None])[0]
</span><del>-        body = yield readBody(response)
-        self.eventChanged(href, etag, scheduleTag, body)
</del><ins>+        self.eventChanged(href, etag, scheduleTag, responseBody)
</ins><span class="cx">         returnValue(response)
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="lines">@@ -1988,7 +2013,7 @@
</span><span class="cx">         if len(users) &gt; 75:
</span><span class="cx">             label_suffix = &quot;huge&quot;
</span><span class="cx"> 
</span><del>-        response = yield self._request(
</del><ins>+        response, responseBody = yield self._request(
</ins><span class="cx">             OK, 'POST', outbox,
</span><span class="cx">             Headers({
</span><span class="cx">                     'content-type': ['text/calendar'],
</span><span class="lines">@@ -2006,8 +2031,7 @@
</span><span class="cx">             }),
</span><span class="cx">             method_label=&quot;POST{fb-%s}&quot; % (label_suffix,),
</span><span class="cx">         )
</span><del>-        body = yield readBody(response)
-        returnValue(body)
</del><ins>+        returnValue(responseBody)
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     @inlineCallbacks
</span><span class="lines">@@ -2017,15 +2041,14 @@
</span><span class="cx">         headers = Headers({
</span><span class="cx">             'Content-Disposition': ['attachment; filename=&quot;{}&quot;'.format(filename)]
</span><span class="cx">         })
</span><del>-        response = yield self._request(
</del><ins>+        response, responseBody = yield self._request(
</ins><span class="cx">             CREATED,
</span><span class="cx">             'POST',
</span><span class="cx">             url,
</span><span class="cx">             headers=headers,
</span><span class="cx">             body=StringProducer(content),
</span><del>-            method_label=&quot;POST{attach}&quot;
</del><ins>+            method_label=&quot;POST{attachment}&quot;
</ins><span class="cx">         )
</span><del>-        body = yield readBody(response)
</del><span class="cx"> 
</span><span class="cx">         # We don't want to download an attachment we uploaded, so look for the
</span><span class="cx">         # Cal-Managed-Id: and Location: headers and remember those
</span><span class="lines">@@ -2034,7 +2057,7 @@
</span><span class="cx">         self._attachments[managedId] = location
</span><span class="cx"> 
</span><span class="cx">         yield self.updateEvent(href)
</span><del>-        returnValue(body)
</del><ins>+        returnValue(responseBody)
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     @inlineCallbacks
</span><span class="lines">@@ -2043,7 +2066,10 @@
</span><span class="cx">         # If we've already downloaded this managedId, skip it.
</span><span class="cx">         if managedId not in self._attachments:
</span><span class="cx">             self._attachments[managedId] = href
</span><del>-            yield self._newOperation(&quot;download&quot;, self._get(href, 200))
</del><ins>+            yield self._newOperation(
+                &quot;download&quot;,
+                self._get(href, (OK, FORBIDDEN), method_label=&quot;GET{attachment}&quot;)
+            )
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     @inlineCallbacks
</span><span class="lines">@@ -2051,7 +2077,7 @@
</span><span class="cx">         headers = Headers({
</span><span class="cx">             'content-type': ['text/xml']
</span><span class="cx">         })
</span><del>-        response = yield self._request(
</del><ins>+        response, responseBody = yield self._request(
</ins><span class="cx">             (OK, CREATED, MULTI_STATUS),
</span><span class="cx">             'POST',
</span><span class="cx">             self.server[&quot;uri&quot;] + href,
</span><span class="lines">@@ -2059,8 +2085,7 @@
</span><span class="cx">             body=StringProducer(content),
</span><span class="cx">             method_label=label
</span><span class="cx">         )
</span><del>-        body = yield readBody(response)
-        returnValue(body)
</del><ins>+        returnValue(responseBody)
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="lines">@@ -2496,7 +2521,7 @@
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx"> class RequestLogger(object):
</span><del>-    format = u&quot;%(user)s request %(code)s%(success)s[%(duration)5.2f s] %(method)8s %(url)s&quot;
</del><ins>+    format = u&quot;%(user)s %(client)s request %(code)s%(success)s[%(duration)5.2f s] %(method)8s %(url)s&quot;
</ins><span class="cx">     success = u&quot;\N{CHECK MARK}&quot;
</span><span class="cx">     failure = u&quot;\N{BALLOT X}&quot;
</span><span class="cx"> 
</span><span class="lines">@@ -2508,6 +2533,7 @@
</span><span class="cx">                 url=urlunparse(('', '') + urlparse(event['url'])[2:]),
</span><span class="cx">                 code=event['code'],
</span><span class="cx">                 duration=event['duration'],
</span><ins>+                client=event['client_type'],
</ins><span class="cx">             )
</span><span class="cx"> 
</span><span class="cx">             if event['success']:
</span><span class="lines">@@ -2516,7 +2542,28 @@
</span><span class="cx">                 formatArgs['success'] = self.failure
</span><span class="cx">             print((self.format % formatArgs).encode('utf-8'))
</span><span class="cx"> 
</span><ins>+            if not event['success']:
+                body = event['body']
+                if isinstance(body, StringProducer):
+                    body = body._body
+                if body:
+                    body = body[:5000]
</ins><span class="cx"> 
</span><ins>+                responseBody = event['responseBody']
+                if responseBody:
+                    responseBody = responseBody[:5000]
+
+                print(&quot;=&quot; * 80)
+                print(&quot;INCORRECT ERROR CODE: {}&quot;.format(event['code']))
+                print(&quot;URL: {}&quot;.format(event['url']))
+                print(&quot;METHOD: {}&quot;.format(event['method']))
+                print(&quot;USER: {}&quot;.format(event['user']))
+                print(&quot;REQUEST BODY:\n{}&quot;.format(body))
+                print(&quot;RESPONSE BODY:\n{}&quot;.format(responseBody))
+                print(&quot;=&quot; * 80)
+
+
+
</ins><span class="cx">     def report(self, output):
</span><span class="cx">         pass
</span><span class="cx"> 
</span></span></pre></div>
<a id="CalendarServertrunkcontribperformanceloadtestpopulationpy"></a>
<div class="modfile"><h4>Modified: CalendarServer/trunk/contrib/performance/loadtest/population.py (15587 => 15588)</h4>
<pre class="diff"><span>
<span class="info">--- 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)
</span><span class="lines">@@ -516,7 +516,10 @@
</span><span class="cx">         # Get means for each type of method
</span><span class="cx">         means = {}
</span><span class="cx">         for method, results in self._perMethodTimes.items():
</span><del>-            means[method] = mean([duration for success, duration in results if success])
</del><ins>+            try:
+                means[method] = mean([duration for success, duration in results if success])
+            except ZeroDivisionError:
+                pass # Ignore the case where all samples were unsuccessful?
</ins><span class="cx"> 
</span><span class="cx">         # Determine percentage differences with weighting
</span><span class="cx">         differences = []
</span></span></pre></div>
<a id="CalendarServertrunkcontribperformanceloadtestprofilespy"></a>
<div class="modfile"><h4>Modified: CalendarServer/trunk/contrib/performance/loadtest/profiles.py (15587 => 15588)</h4>
<pre class="diff"><span>
<span class="info">--- 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)
</span><span class="lines">@@ -24,6 +24,7 @@
</span><span class="cx"> import json
</span><span class="cx"> import random
</span><span class="cx"> import sys
</span><ins>+from urlparse import urljoin, urlparse
</ins><span class="cx"> from uuid import uuid4
</span><span class="cx"> 
</span><span class="cx"> from caldavclientlibrary.protocol.caldav.definitions import caldavxml
</span><span class="lines">@@ -260,6 +261,7 @@
</span><span class="cx">             120 * 60
</span><span class="cx">         ]),
</span><span class="cx">         recurrenceDistribution=RecurrenceDistribution(False),
</span><ins>+        fileSizeDistribution=NormalDistribution(1024, 1),
</ins><span class="cx">     ):
</span><span class="cx">         self.enabled = enabled
</span><span class="cx">         self._sendInvitationDistribution = sendInvitationDistribution
</span><span class="lines">@@ -269,6 +271,7 @@
</span><span class="cx">         self._eventStartDistribution = eventStartDistribution
</span><span class="cx">         self._eventDurationDistribution = eventDurationDistribution
</span><span class="cx">         self._recurrenceDistribution = recurrenceDistribution
</span><ins>+        self._fileSizeDistribution = fileSizeDistribution
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     def run(self):
</span><span class="lines">@@ -333,7 +336,7 @@
</span><span class="cx">             return succeed(None)
</span><span class="cx"> 
</span><span class="cx">         # Find calendars which are eligible for invites
</span><del>-        calendars = self._calendarsOfType(caldavxml.calendar, &quot;VEVENT&quot;)
</del><ins>+        calendars = self._calendarsOfType(caldavxml.calendar, &quot;VEVENT&quot;, justOwned=True)
</ins><span class="cx"> 
</span><span class="cx">         while calendars:
</span><span class="cx">             # Pick one at random from which to try to create an event
</span><span class="lines">@@ -368,8 +371,9 @@
</span><span class="cx">                 except CannotAddAttendee:
</span><span class="cx">                     continue
</span><span class="cx"> 
</span><ins>+            attachmentSize = int(self._fileSizeDistribution.sample())
</ins><span class="cx">             href = '%s%s.ics' % (calendar.url, uid)
</span><del>-            d = self._client.addInvite(href, vcalendar)
</del><ins>+            d = self._client.addInvite(href, vcalendar, attachmentSize=attachmentSize)
</ins><span class="cx">             return self._newOperation(&quot;invite&quot;, d)
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="lines">@@ -561,12 +565,17 @@
</span><span class="cx">             for attachment in attachments:
</span><span class="cx">                 attachmentHref = attachment.value()
</span><span class="cx">                 managedId = attachment.parameterValue('MANAGED-ID')
</span><ins>+                attachmentHref = self._normalizeHref(attachmentHref)
+
</ins><span class="cx">                 self._reactor.callLater(
</span><span class="cx">                     0, self._client.getAttachment, attachmentHref, managedId
</span><span class="cx">                 )
</span><span class="cx"> 
</span><ins>+    def _normalizeHref(self, href):
+        return urljoin(self._client._managed_attachments_server_url, urlparse(href).path)
</ins><span class="cx"> 
</span><span class="cx"> 
</span><ins>+
</ins><span class="cx"> class Eventer(ProfileBase):
</span><span class="cx">     &quot;&quot;&quot;
</span><span class="cx">     A Calendar user who creates new events.
</span><span class="lines">@@ -600,12 +609,14 @@
</span><span class="cx">             120 * 60
</span><span class="cx">         ]),
</span><span class="cx">         recurrenceDistribution=RecurrenceDistribution(False),
</span><ins>+        fileSizeDistribution=NormalDistribution(1024, 1),
</ins><span class="cx">     ):
</span><span class="cx">         self.enabled = enabled
</span><span class="cx">         self._interval = interval
</span><span class="cx">         self._eventStartDistribution = eventStartDistribution
</span><span class="cx">         self._eventDurationDistribution = eventDurationDistribution
</span><span class="cx">         self._recurrenceDistribution = recurrenceDistribution
</span><ins>+        self._fileSizeDistribution = fileSizeDistribution
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     def run(self):
</span><span class="lines">@@ -642,8 +653,9 @@
</span><span class="cx">         if rrule is not None:
</span><span class="cx">             vevent.addProperty(Property(None, None, None, pycalendar=rrule))
</span><span class="cx"> 
</span><ins>+        attachmentSize = int(self._fileSizeDistribution.sample())
</ins><span class="cx">         href = '%s%s.ics' % (calendar.url, uid)
</span><del>-        d = self._client.addEvent(href, vcalendar)
</del><ins>+        d = self._client.addEvent(href, vcalendar, attachmentSize=attachmentSize)
</ins><span class="cx">         return self._newOperation(&quot;create&quot;, d)
</span><span class="cx"> 
</span><span class="cx"> 
</span></span></pre></div>
<a id="CalendarServertrunkcontribperformanceloadtestrequestdataOS_X_10_11poll_calendarhome_depth1_propfindrequest"></a>
<div class="modfile"><h4>Modified: CalendarServer/trunk/contrib/performance/loadtest/request-data/OS_X_10_11/poll_calendarhome_depth1_propfind.request (15587 => 15588)</h4>
<pre class="diff"><span>
<span class="info">--- 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)
</span><span class="lines">@@ -39,5 +39,6 @@
</span><span class="cx">     &lt;B:supported-calendar-component-sets xmlns:B=&quot;urn:ietf:params:xml:ns:caldav&quot;/&gt;
</span><span class="cx">     &lt;A:supported-report-set/&gt;
</span><span class="cx">     &lt;A:sync-token/&gt;
</span><ins>+    &lt;B:managed-attachments-server-URL xmlns:B=&quot;urn:ietf:params:xml:ns:caldav&quot;/&gt;
</ins><span class="cx">   &lt;/A:prop&gt;
</span><span class="cx"> &lt;/A:propfind&gt;
</span></span></pre></div>
<a id="CalendarServertrunkcontribperformanceloadtesttest_icalpy"></a>
<div class="modfile"><h4>Modified: CalendarServer/trunk/contrib/performance/loadtest/test_ical.py (15587 => 15588)</h4>
<pre class="diff"><span>
<span class="info">--- 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)
</span><span class="lines">@@ -1358,7 +1358,7 @@
</span><span class="cx">             response = MemoryResponse(
</span><span class="cx">                 ('HTTP', '1', '1'), CREATED, &quot;Created&quot;, Headers({&quot;etag&quot;: [&quot;foo&quot;]}),
</span><span class="cx">                 StringProducer(&quot;&quot;))
</span><del>-            result.callback(response)
</del><ins>+            result.callback((response, &quot;&quot;))
</ins><span class="cx">         finished.addCallback(requested)
</span><span class="cx"> 
</span><span class="cx">         return d
</span><span class="lines">@@ -1399,7 +1399,7 @@
</span><span class="cx">                 ('HTTP', '1', '1'), MULTI_STATUS, &quot;MultiStatus&quot;, Headers({}),
</span><span class="cx">                 StringProducer(&quot;&lt;?xml version='1.0' encoding='UTF-8'?&gt;&lt;multistatus xmlns='DAV:' /&gt;&quot;))
</span><span class="cx"> 
</span><del>-            returnValue(response)
</del><ins>+            returnValue((response, &quot;&lt;?xml version='1.0' encoding='UTF-8'?&gt;&lt;multistatus xmlns='DAV:' /&gt;&quot;))
</ins><span class="cx"> 
</span><span class="cx">         @inlineCallbacks
</span><span class="cx">         def _testPost(*args, **kwargs):
</span><span class="lines">@@ -1418,7 +1418,7 @@
</span><span class="cx">                 ('HTTP', '1', '1'), OK, &quot;OK&quot;, Headers({}),
</span><span class="cx">                 StringProducer(&quot;&quot;))
</span><span class="cx"> 
</span><del>-            returnValue(response)
</del><ins>+            returnValue((response, &quot;&quot;))
</ins><span class="cx"> 
</span><span class="cx">         def _testPost02(*args, **kwargs):
</span><span class="cx">             return _testPost(*args, attendee=&quot;ATTENDEE:mailto:user02@example.com&quot;, **kwargs)
</span><span class="lines">@@ -1445,7 +1445,7 @@
</span><span class="cx">                 ('HTTP', '1', '1'), CREATED, &quot;Created&quot;, Headers({}),
</span><span class="cx">                 StringProducer(&quot;&quot;))
</span><span class="cx"> 
</span><del>-            returnValue(response)
</del><ins>+            returnValue((response, &quot;&quot;))
</ins><span class="cx"> 
</span><span class="cx">         def _testGet(*args, **kwargs):
</span><span class="cx">             expectedResponseCode, method, url = args
</span><span class="lines">@@ -1458,7 +1458,7 @@
</span><span class="cx">                 ('HTTP', '1', '1'), OK, &quot;OK&quot;, Headers({&quot;etag&quot;: [&quot;foo&quot;]}),
</span><span class="cx">                 StringProducer(EVENT_INVITE))
</span><span class="cx"> 
</span><del>-            return succeed(response)
</del><ins>+            return succeed((response, EVENT_INVITE))
</ins><span class="cx"> 
</span><span class="cx">         requests = [_testReport, _testPost02, _testReport, _testPost03, _testPut, _testGet]
</span><span class="cx"> 
</span><span class="lines">@@ -1499,7 +1499,7 @@
</span><span class="cx">         response = MemoryResponse(
</span><span class="cx">             ('HTTP', '1', '1'), NO_CONTENT, &quot;No Content&quot;, None,
</span><span class="cx">             StringProducer(&quot;&quot;))
</span><del>-        result.callback(response)
</del><ins>+        result.callback((response, &quot;&quot;))
</ins><span class="cx">         return d
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="lines">@@ -1935,9 +1935,13 @@
</span><span class="cx">         self.assertEqual((MULTI_STATUS, FORBIDDEN), expectedResponseCode)
</span><span class="cx"> 
</span><span class="cx">         result.callback(
</span><del>-            MemoryResponse(
-                ('HTTP', '1', '1'), MULTI_STATUS, &quot;Multi-status&quot;, None,
-                StringProducer(self._CALENDAR_PROPFIND_RESPONSE_BODY)))
</del><ins>+            (
+                MemoryResponse(
+                    ('HTTP', '1', '1'), MULTI_STATUS, &quot;Multi-status&quot;, None,
+                    StringProducer(self._CALENDAR_PROPFIND_RESPONSE_BODY)),
+                self._CALENDAR_PROPFIND_RESPONSE_BODY
+            )
+        )
</ins><span class="cx"> 
</span><span class="cx">         result, req = requests.pop(0)
</span><span class="cx">         expectedResponseCode, method, url, _ignore_headers, _ignore_body = req
</span><span class="lines">@@ -1949,9 +1953,13 @@
</span><span class="cx">         del self.client._events[&quot;/something/anotherthing.ics&quot;]
</span><span class="cx"> 
</span><span class="cx">         result.callback(
</span><del>-            MemoryResponse(
-                ('HTTP', '1', '1'), MULTI_STATUS, &quot;Multi-status&quot;, None,
-                StringProducer(self._CALENDAR_REPORT_RESPONSE_BODY)))
</del><ins>+            (
+                MemoryResponse(
+                    ('HTTP', '1', '1'), MULTI_STATUS, &quot;Multi-status&quot;, None,
+                    StringProducer(self._CALENDAR_REPORT_RESPONSE_BODY)),
+                self._CALENDAR_REPORT_RESPONSE_BODY
+            )
+        )
</ins><span class="cx"> 
</span><span class="cx">         # Verify that processing proceeded to the response after the one with a
</span><span class="cx">         # 404 status.
</span><span class="lines">@@ -1978,9 +1986,13 @@
</span><span class="cx">         self.assertEqual((MULTI_STATUS, FORBIDDEN), expectedResponseCode)
</span><span class="cx"> 
</span><span class="cx">         result.callback(
</span><del>-            MemoryResponse(
-                ('HTTP', '1', '1'), MULTI_STATUS, &quot;Multi-status&quot;, None,
-                StringProducer(self._CALENDAR_PROPFIND_RESPONSE_BODY)))
</del><ins>+            (
+                MemoryResponse(
+                    ('HTTP', '1', '1'), MULTI_STATUS, &quot;Multi-status&quot;, None,
+                    StringProducer(self._CALENDAR_PROPFIND_RESPONSE_BODY)),
+                self._CALENDAR_PROPFIND_RESPONSE_BODY
+            )
+        )
</ins><span class="cx"> 
</span><span class="cx">         result, req = requests.pop(0)
</span><span class="cx">         expectedResponseCode, method, url, _ignore_headers, _ignore_body = req
</span><span class="lines">@@ -1989,9 +2001,13 @@
</span><span class="cx">         self.assertEqual((MULTI_STATUS,), expectedResponseCode)
</span><span class="cx"> 
</span><span class="cx">         result.callback(
</span><del>-            MemoryResponse(
-                ('HTTP', '1', '1'), MULTI_STATUS, &quot;Multi-status&quot;, None,
-                StringProducer(self._CALENDAR_REPORT_RESPONSE_BODY_1)))
</del><ins>+            (
+                MemoryResponse(
+                    ('HTTP', '1', '1'), MULTI_STATUS, &quot;Multi-status&quot;, None,
+                    StringProducer(self._CALENDAR_REPORT_RESPONSE_BODY_1)),
+                self._CALENDAR_REPORT_RESPONSE_BODY_1
+            )
+        )
</ins><span class="cx"> 
</span><span class="cx">         self.assertTrue(self.client._events['/something/anotherthing.ics'].etag is not None)
</span><span class="cx">         self.assertTrue(self.client._events['/something/else.ics'].etag is None)
</span><span class="lines">@@ -2003,9 +2019,13 @@
</span><span class="cx">         self.assertEqual((MULTI_STATUS,), expectedResponseCode)
</span><span class="cx"> 
</span><span class="cx">         result.callback(
</span><del>-            MemoryResponse(
-                ('HTTP', '1', '1'), MULTI_STATUS, &quot;Multi-status&quot;, None,
-                StringProducer(self._CALENDAR_REPORT_RESPONSE_BODY_2)))
</del><ins>+            (
+                MemoryResponse(
+                    ('HTTP', '1', '1'), MULTI_STATUS, &quot;Multi-status&quot;, None,
+                    StringProducer(self._CALENDAR_REPORT_RESPONSE_BODY_2)),
+                self._CALENDAR_REPORT_RESPONSE_BODY_2
+            )
+        )
</ins><span class="cx"> 
</span><span class="cx">         self.assertTrue(self.client._events['/something/anotherthing.ics'].etag is not None)
</span><span class="cx">         self.assertTrue(self.client._events['/something/else.ics'].etag is not None)
</span><span class="lines">@@ -2077,7 +2097,7 @@
</span><span class="cx">             response = MemoryResponse(
</span><span class="cx">                 ('HTTP', '1', '1'), OK, &quot;Ok&quot;, Headers({}),
</span><span class="cx">                 StringProducer(&quot;&quot;))
</span><del>-            result.callback(response)
</del><ins>+            result.callback((response, &quot;&quot;))
</ins><span class="cx">         finished.addCallback(requested)
</span><span class="cx"> 
</span><span class="cx">         return d
</span></span></pre></div>
<a id="CalendarServertrunkcontribperformanceloadtesttest_profilespy"></a>
<div class="modfile"><h4>Modified: CalendarServer/trunk/contrib/performance/loadtest/test_profiles.py (15587 => 15588)</h4>
<pre class="diff"><span>
<span class="info">--- 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)
</span><span class="lines">@@ -31,7 +31,7 @@
</span><span class="cx"> 
</span><span class="cx"> from twistedcaldav.ical import Component, Property
</span><span class="cx"> 
</span><del>-from contrib.performance.loadtest.profiles import Eventer, Inviter, Accepter, OperationLogger, AlarmAcknowledger
</del><ins>+from contrib.performance.loadtest.profiles import Eventer, Inviter, Accepter, OperationLogger, AlarmAcknowledger, AttachmentDownloader
</ins><span class="cx"> from contrib.performance.loadtest.population import Populator, CalendarClientSimulator
</span><span class="cx"> from contrib.performance.loadtest.ical import IncorrectResponseCode, Calendar, Event, BaseClient
</span><span class="cx"> from contrib.performance.loadtest.sim import _DirectoryRecord
</span><span class="lines">@@ -258,13 +258,13 @@
</span><span class="cx">         return path
</span><span class="cx"> 
</span><span class="cx"> 
</span><del>-    def addEvent(self, href, vevent):
</del><ins>+    def addEvent(self, href, vevent, attachmentSize=0):
</ins><span class="cx">         self._events[href] = Event(self.serializePath, href, None, vevent)
</span><span class="cx">         return succeed(None)
</span><span class="cx"> 
</span><span class="cx"> 
</span><del>-    def addInvite(self, href, vevent):
-        return self.addEvent(href, vevent)
</del><ins>+    def addInvite(self, href, vevent, attachmentSize=0):
+        return self.addEvent(href, vevent, attachmentSize=attachmentSize)
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     def deleteEvent(self, href,):
</span><span class="lines">@@ -291,11 +291,14 @@
</span><span class="cx"> 
</span><span class="cx">     def changeEventAttendee(self, href, old, new):
</span><span class="cx">         if href in self.rescheduled:
</span><del>-            return fail(IncorrectResponseCode(
-                NO_CONTENT,
-                Response(
-                    ('HTTP', 1, 1), PRECONDITION_FAILED,
-                    'Precondition Failed', None, None))
</del><ins>+            return fail(
+                IncorrectResponseCode(
+                    NO_CONTENT,
+                    Response(
+                        ('HTTP', 1, 1), PRECONDITION_FAILED,
+                        'Precondition Failed', None, None),
+                    None
+                ),
</ins><span class="cx">             )
</span><span class="cx"> 
</span><span class="cx">         vevent = self._events[href].component
</span><span class="lines">@@ -851,7 +854,7 @@
</span><span class="cx">             NO_CONTENT,
</span><span class="cx">             Response(
</span><span class="cx">                 ('HTTP', 1, 1), PRECONDITION_FAILED,
</span><del>-                'Precondition Failed', None, None))
</del><ins>+                'Precondition Failed', None, None), None)
</ins><span class="cx">         )
</span><span class="cx">         accepter = Accepter(clock, self.sim, client, userNumber)
</span><span class="cx">         accepter.eventChanged(inboxEvent.url)
</span><span class="lines">@@ -1034,7 +1037,25 @@
</span><span class="cx">         # XXX Vary the event period/interval and the uid
</span><span class="cx"> 
</span><span class="cx"> 
</span><ins>+class AttachmentDownloaderTests(TestCase):
+    &quot;&quot;&quot;
+    Tests for L{AttachmentDownloader}.
+    &quot;&quot;&quot;
</ins><span class="cx"> 
</span><ins>+    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 = &quot;https://thisserver:8443&quot;
+        downloader = AttachmentDownloader(Clock(), self.sim, client, None)
+        self.assertEquals(
+            downloader._normalizeHref(&quot;http://otherserver/attachment/url&quot;),
+            &quot;https://thisserver:8443/attachment/url&quot;
+        )
+
+
</ins><span class="cx"> class OperationLoggerTests(TestCase):
</span><span class="cx">     &quot;&quot;&quot;
</span><span class="cx">     Tests for L{OperationLogger}.
</span></span></pre></div>
<a id="CalendarServertrunkrequirementsdevtxt"></a>
<div class="modfile"><h4>Modified: CalendarServer/trunk/requirements-dev.txt (15587 => 15588)</h4>
<pre class="diff"><span>
<span class="info">--- 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)
</span><span class="lines">@@ -4,5 +4,5 @@
</span><span class="cx"> mockldap
</span><span class="cx"> q
</span><span class="cx"> tl.eggdeps
</span><del>---editable svn+http://svn.calendarserver.org/repository/calendarserver/CalDAVClientLibrary/trunk@15425#egg=CalDAVClientLibrary
</del><ins>+--editable svn+http://svn.calendarserver.org/repository/calendarserver/CalDAVClientLibrary/trunk@15578#egg=CalDAVClientLibrary
</ins><span class="cx"> --editable svn+http://svn.calendarserver.org/repository/calendarserver/CalDAVTester/trunk@15551#egg=CalDAVTester
</span></span></pre>
</div>
</div>

</body>
</html>