[CalendarServer-changes] [9708] CalendarServer/trunk/contrib/performance/sqlusage
source_changes at macosforge.org
source_changes at macosforge.org
Tue Aug 14 15:17:59 PDT 2012
Revision: 9708
http://trac.macosforge.org/projects/calendarserver/changeset/9708
Author: cdaboo at apple.com
Date: 2012-08-14 15:17:59 -0700 (Tue, 14 Aug 2012)
Log Message:
-----------
Add put and invite tests. Add some more command line options. Make it easier to handle multiple users.
Modified Paths:
--------------
CalendarServer/trunk/contrib/performance/sqlusage/requests/httpTests.py
CalendarServer/trunk/contrib/performance/sqlusage/requests/multiget.py
CalendarServer/trunk/contrib/performance/sqlusage/requests/propfind.py
CalendarServer/trunk/contrib/performance/sqlusage/requests/query.py
CalendarServer/trunk/contrib/performance/sqlusage/requests/sync.py
CalendarServer/trunk/contrib/performance/sqlusage/sqlusage.py
Added Paths:
-----------
CalendarServer/trunk/contrib/performance/sqlusage/requests/invite.py
CalendarServer/trunk/contrib/performance/sqlusage/requests/put.py
Modified: CalendarServer/trunk/contrib/performance/sqlusage/requests/httpTests.py
===================================================================
--- CalendarServer/trunk/contrib/performance/sqlusage/requests/httpTests.py 2012-08-14 21:42:08 UTC (rev 9707)
+++ CalendarServer/trunk/contrib/performance/sqlusage/requests/httpTests.py 2012-08-14 22:17:59 UTC (rev 9708)
@@ -30,14 +30,13 @@
self.rows = rows
self.timing = timing
- def __init__(self, label, session, href, logFilePath):
+ def __init__(self, label, sessions, logFilePath):
"""
@param label: label used to identify the test
@type label: C{str}
"""
self.label = label
- self.session = session
- self.baseHref = href
+ self.sessions = sessions
self.logFilePath = logFilePath
self.result = None
Added: CalendarServer/trunk/contrib/performance/sqlusage/requests/invite.py
===================================================================
--- CalendarServer/trunk/contrib/performance/sqlusage/requests/invite.py (rev 0)
+++ CalendarServer/trunk/contrib/performance/sqlusage/requests/invite.py 2012-08-14 22:17:59 UTC (rev 9708)
@@ -0,0 +1,92 @@
+##
+# Copyright (c) 2012 Apple Inc. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+##
+
+from caldavclientlibrary.protocol.url import URL
+from contrib.performance.sqlusage.requests.httpTests import HTTPTestBase
+from pycalendar.datetime import PyCalendarDateTime
+from twext.web2.dav.util import joinURL
+from caldavclientlibrary.protocol.webdav.definitions import davxml
+
+ICAL = """BEGIN:VCALENDAR
+CALSCALE:GREGORIAN
+PRODID:-//Example Inc.//Example Calendar//EN
+VERSION:2.0
+BEGIN:VTIMEZONE
+LAST-MODIFIED:20040110T032845Z
+TZID:US/Eastern
+BEGIN:DAYLIGHT
+DTSTART:20000404T020000
+RRULE:FREQ=YEARLY;BYDAY=1SU;BYMONTH=4
+TZNAME:EDT
+TZOFFSETFROM:-0500
+TZOFFSETTO:-0400
+END:DAYLIGHT
+BEGIN:STANDARD
+DTSTART:20001026T020000
+RRULE:FREQ=YEARLY;BYDAY=-1SU;BYMONTH=10
+TZNAME:EST
+TZOFFSETFROM:-0400
+TZOFFSETTO:-0500
+END:STANDARD
+END:VTIMEZONE
+BEGIN:VEVENT
+DTSTAMP:20051222T205953Z
+CREATED:20060101T150000Z
+DTSTART;TZID=US/Eastern:%d0101T100000
+DURATION:PT1H
+SUMMARY:event 1
+UID:invite-ics
+ORGANIZER:mailto:user02 at example.com
+ATTENDEE:mailto:user02 at example.com
+ATTENDEE:mailto:user01 at example.com
+END:VEVENT
+END:VCALENDAR
+""".replace("\n", "\r\n")
+
+class InviteTest(HTTPTestBase):
+ """
+ A PUT operation (invite)
+ """
+
+ def doRequest(self):
+ """
+ Execute the actual HTTP request.
+ """
+
+ # Invite as user02
+ now = PyCalendarDateTime.getNowUTC()
+ href = joinURL(self.sessions[1].calendarHref, "organizer.ics")
+ self.sessions[1].writeData(URL(path=href), ICAL % (now.getYear() + 1,), "text/calendar")
+
+ def cleanup(self):
+ """
+ Do some cleanup after the real request.
+ """
+ # Remove created resources
+ href = joinURL(self.sessions[1].calendarHref, "organizer.ics")
+ self.sessions[1].deleteResource(URL(path=href))
+
+ # Remove the attendee event and inbox items
+ props = (davxml.resourcetype,)
+ results = self.sessions[0].getPropertiesOnHierarchy(URL(path=self.sessions[0].calendarHref), props)
+ for href in results.keys():
+ if len(href.split("/")[-1]) > 10:
+ self.sessions[0].deleteResource(URL(path=href))
+ results = self.sessions[0].getPropertiesOnHierarchy(URL(path=self.sessions[0].inboxHref), props)
+ for href in results.keys():
+ if href != self.sessions[0].inboxHref:
+ self.sessions[0].deleteResource(URL(path=href))
+
Modified: CalendarServer/trunk/contrib/performance/sqlusage/requests/multiget.py
===================================================================
--- CalendarServer/trunk/contrib/performance/sqlusage/requests/multiget.py 2012-08-14 21:42:08 UTC (rev 9707)
+++ CalendarServer/trunk/contrib/performance/sqlusage/requests/multiget.py 2012-08-14 22:17:59 UTC (rev 9708)
@@ -26,15 +26,15 @@
A multiget operation
"""
- def __init__(self, label, session, href, logFilePath, count):
- super(MultigetTest, self).__init__(label, session, href, logFilePath)
+ def __init__(self, label, sessions, logFilePath, count):
+ super(MultigetTest, self).__init__(label, sessions, logFilePath)
self.count = count
def doRequest(self):
"""
Execute the actual HTTP request.
"""
- hrefs = [joinURL(self.baseHref, "%d.ics" % (i+1,)) for i in range(self.count)]
+ hrefs = [joinURL(self.sessions[0].calendarHref, "%d.ics" % (i+1,)) for i in range(self.count)]
props = (
davxml.getetag,
caldavxml.calendar_data,
@@ -42,12 +42,12 @@
)
# Create CalDAV multiget
- request = Multiget(self.session, self.baseHref, hrefs, props)
+ request = Multiget(self.sessions[0], self.sessions[0].calendarHref, hrefs, props)
result = ResponseDataString()
request.setOutput(result)
# Process it
- self.session.runSession(request)
+ self.sessions[0].runSession(request)
# If its a 207 we want to parse the XML
if request.getStatusCode() == statuscodes.MultiStatus:
Modified: CalendarServer/trunk/contrib/performance/sqlusage/requests/propfind.py
===================================================================
--- CalendarServer/trunk/contrib/performance/sqlusage/requests/propfind.py 2012-08-14 21:42:08 UTC (rev 9707)
+++ CalendarServer/trunk/contrib/performance/sqlusage/requests/propfind.py 2012-08-14 22:17:59 UTC (rev 9708)
@@ -25,8 +25,8 @@
A propfind operation
"""
- def __init__(self, label, session, href, logFilePath, depth=1):
- super(PropfindTest, self).__init__(label, session, href, logFilePath)
+ def __init__(self, label, sessions, logFilePath, depth=1):
+ super(PropfindTest, self).__init__(label, sessions, logFilePath)
self.depth = headers.Depth1 if depth == 1 else headers.Depth0
def doRequest(self):
@@ -39,12 +39,12 @@
)
# Create WebDAV propfind
- request = PropFind(self.session, self.baseHref, self.depth, props)
+ request = PropFind(self.sessions[0], self.sessions[0].calendarHref, self.depth, props)
result = ResponseDataString()
request.setOutput(result)
# Process it
- self.session.runSession(request)
+ self.sessions[0].runSession(request)
# If its a 207 we want to parse the XML
if request.getStatusCode() == statuscodes.MultiStatus:
Added: CalendarServer/trunk/contrib/performance/sqlusage/requests/put.py
===================================================================
--- CalendarServer/trunk/contrib/performance/sqlusage/requests/put.py (rev 0)
+++ CalendarServer/trunk/contrib/performance/sqlusage/requests/put.py 2012-08-14 22:17:59 UTC (rev 9708)
@@ -0,0 +1,75 @@
+##
+# Copyright (c) 2012 Apple Inc. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+##
+
+from caldavclientlibrary.protocol.url import URL
+from contrib.performance.sqlusage.requests.httpTests import HTTPTestBase
+from pycalendar.datetime import PyCalendarDateTime
+from twext.web2.dav.util import joinURL
+
+ICAL = """BEGIN:VCALENDAR
+CALSCALE:GREGORIAN
+PRODID:-//Example Inc.//Example Calendar//EN
+VERSION:2.0
+BEGIN:VTIMEZONE
+LAST-MODIFIED:20040110T032845Z
+TZID:US/Eastern
+BEGIN:DAYLIGHT
+DTSTART:20000404T020000
+RRULE:FREQ=YEARLY;BYDAY=1SU;BYMONTH=4
+TZNAME:EDT
+TZOFFSETFROM:-0500
+TZOFFSETTO:-0400
+END:DAYLIGHT
+BEGIN:STANDARD
+DTSTART:20001026T020000
+RRULE:FREQ=YEARLY;BYDAY=-1SU;BYMONTH=10
+TZNAME:EST
+TZOFFSETFROM:-0400
+TZOFFSETTO:-0500
+END:STANDARD
+END:VTIMEZONE
+BEGIN:VEVENT
+DTSTAMP:20051222T205953Z
+CREATED:20060101T150000Z
+DTSTART;TZID=US/Eastern:%d0101T100000
+DURATION:PT1H
+SUMMARY:event 1
+UID:put-ics
+END:VEVENT
+END:VCALENDAR
+""".replace("\n", "\r\n")
+
+class PutTest(HTTPTestBase):
+ """
+ A PUT operation (non-invite)
+ """
+
+ def doRequest(self):
+ """
+ Execute the actual HTTP request.
+ """
+
+ now = PyCalendarDateTime.getNowUTC()
+ href = joinURL(self.sessions[0].calendarHref, "put.ics")
+ self.sessions[0].writeData(URL(path=href), ICAL % (now.getYear() + 1,), "text/calendar")
+
+ def cleanup(self):
+ """
+ Do some cleanup after the real request.
+ """
+ # Remove created resources
+ href = joinURL(self.sessions[0].calendarHref, "put.ics")
+ self.sessions[0].deleteResource(URL(path=href))
Modified: CalendarServer/trunk/contrib/performance/sqlusage/requests/query.py
===================================================================
--- CalendarServer/trunk/contrib/performance/sqlusage/requests/query.py 2012-08-14 21:42:08 UTC (rev 9707)
+++ CalendarServer/trunk/contrib/performance/sqlusage/requests/query.py 2012-08-14 22:17:59 UTC (rev 9708)
@@ -60,8 +60,8 @@
A sync operation
"""
- def __init__(self, label, session, href, logFilePath, count):
- super(QueryTest, self).__init__(label, session, href, logFilePath)
+ def __init__(self, label, sessions, logFilePath, count):
+ super(QueryTest, self).__init__(label, sessions, logFilePath)
self.count = count
def prepare(self):
@@ -74,8 +74,8 @@
self.end = self.start.duplicate()
self.end.offsetHours(1)
for i in range(self.count):
- href = joinURL(self.baseHref, "tr-query-%d.ics" % (i+1,))
- self.session.writeData(URL(path=href), ICAL % (self.start.getText(), i+1,), "text/calendar")
+ href = joinURL(self.sessions[0].calendarHref, "tr-query-%d.ics" % (i+1,))
+ self.sessions[0].writeData(URL(path=href), ICAL % (self.start.getText(), i+1,), "text/calendar")
def doRequest(self):
"""
@@ -87,12 +87,12 @@
)
# Create CalDAV query
- request = QueryVEVENTTimeRange(self.session, self.baseHref, self.start.getText(), self.end.getText(), props)
+ request = QueryVEVENTTimeRange(self.sessions[0], self.sessions[0].calendarHref, self.start.getText(), self.end.getText(), props)
result = ResponseDataString()
request.setOutput(result)
# Process it
- self.session.runSession(request)
+ self.sessions[0].runSession(request)
# If its a 207 we want to parse the XML
if request.getStatusCode() == statuscodes.MultiStatus:
@@ -106,5 +106,5 @@
"""
# Remove created resources
for i in range(self.count):
- href = joinURL(self.baseHref, "tr-query-%d.ics" % (i+1,))
- self.session.deleteResource(URL(path=href))
+ href = joinURL(self.sessions[0].calendarHref, "tr-query-%d.ics" % (i+1,))
+ self.sessions[0].deleteResource(URL(path=href))
Modified: CalendarServer/trunk/contrib/performance/sqlusage/requests/sync.py
===================================================================
--- CalendarServer/trunk/contrib/performance/sqlusage/requests/sync.py 2012-08-14 21:42:08 UTC (rev 9707)
+++ CalendarServer/trunk/contrib/performance/sqlusage/requests/sync.py 2012-08-14 22:17:59 UTC (rev 9708)
@@ -58,8 +58,8 @@
A sync operation
"""
- def __init__(self, label, session, href, logFilePath, full, count):
- super(SyncTest, self).__init__(label, session, href, logFilePath)
+ def __init__(self, label, sessions, logFilePath, full, count):
+ super(SyncTest, self).__init__(label, sessions, logFilePath)
self.full = full
self.count = count
self.synctoken = ""
@@ -70,14 +70,14 @@
"""
if not self.full:
# Get current sync token
- results, _ignore_bad = self.session.getProperties(URL(path=self.baseHref), (davxml.sync_token,))
+ results, _ignore_bad = self.sessions[0].getProperties(URL(path=self.sessions[0].calendarHref), (davxml.sync_token,))
self.synctoken = results[davxml.sync_token]
# Add resources to create required number of changes
now = PyCalendarDateTime.getNowUTC()
for i in range(self.count):
- href = joinURL(self.baseHref, "sync-collection-%d.ics" % (i+1,))
- self.session.writeData(URL(path=href), ICAL % (now.getYear() + 1, i+1,), "text/calendar")
+ href = joinURL(self.sessions[0].calendarHref, "sync-collection-%d.ics" % (i+1,))
+ self.sessions[0].writeData(URL(path=href), ICAL % (now.getYear() + 1, i+1,), "text/calendar")
def doRequest(self):
"""
@@ -89,7 +89,7 @@
)
# Run sync collection
- self.session.syncCollection(URL(path=self.baseHref), self.synctoken, props)
+ self.sessions[0].syncCollection(URL(path=self.sessions[0].calendarHref), self.synctoken, props)
def cleanup(self):
"""
@@ -98,5 +98,5 @@
if not self.full:
# Remove created resources
for i in range(self.count):
- href = joinURL(self.baseHref, "sync-collection-%d.ics" % (i+1,))
- self.session.deleteResource(URL(path=href))
+ href = joinURL(self.sessions[0].calendarHref, "sync-collection-%d.ics" % (i+1,))
+ self.sessions[0].deleteResource(URL(path=href))
Modified: CalendarServer/trunk/contrib/performance/sqlusage/sqlusage.py
===================================================================
--- CalendarServer/trunk/contrib/performance/sqlusage/sqlusage.py 2012-08-14 21:42:08 UTC (rev 9707)
+++ CalendarServer/trunk/contrib/performance/sqlusage/sqlusage.py 2012-08-14 22:17:59 UTC (rev 9708)
@@ -19,13 +19,16 @@
from caldavclientlibrary.protocol.url import URL
from caldavclientlibrary.protocol.webdav.definitions import davxml
from calendarserver.tools import tables
+from contrib.performance.sqlusage.requests.invite import InviteTest
from contrib.performance.sqlusage.requests.multiget import MultigetTest
from contrib.performance.sqlusage.requests.propfind import PropfindTest
+from contrib.performance.sqlusage.requests.put import PutTest
from contrib.performance.sqlusage.requests.query import QueryTest
from contrib.performance.sqlusage.requests.sync import SyncTest
from pycalendar.datetime import PyCalendarDateTime
from twext.web2.dav.util import joinURL
import getopt
+import itertools
import sys
"""
@@ -37,7 +40,7 @@
with calendar size can be plotted.
"""
-EVENT_COUNTS = (0, 1, 5, 10, 25, 50, 75, 100, 200, 300, 400, 500, 1000, 2000, 3000, 4000, 5000)
+EVENT_COUNTS = (0, 1, 5, 10, 50, 100, 500, 1000, 5000)
ICAL = """BEGIN:VCALENDAR
CALSCALE:GREGORIAN
@@ -72,48 +75,63 @@
END:VCALENDAR
""".replace("\n", "\r\n")
+class SQLUsageSession(CalDAVSession):
+
+ def __init__(self, server, port=None, ssl=False, user="", pswd="", principal=None, root=None, logging=False):
+
+ super(SQLUsageSession, self).__init__(server, port, ssl, user, pswd, principal, root, logging)
+ self.homeHref = "/calendars/users/%s/" % (self.user,)
+ self.calendarHref = "/calendars/users/%s/calendar/" % (self.user,)
+ self.inboxHref = "/calendars/users/%s/inbox/" % (self.user,)
+
+
class SQLUsage(object):
- def __init__(self, server, port, user, pswd, logFilePath):
+ def __init__(self, server, port, users, pswds, logFilePath):
self.server = server
self.port = port
- self.user = user
- self.pswd = pswd
+ self.users = users
+ self.pswds = pswds
self.logFilePath = logFilePath
self.requestLabels = []
self.results = {}
self.currentCount = 0
-
- self.userhref = "/calendars/users/%s/" % (self.user,)
- def runLoop(self):
+ def runLoop(self, counts):
- # Make the session
- session = CalDAVSession(self.server, self.port, user=self.user, pswd=self.pswd, root="/")
+ # Make the sessions
+ sessions = [
+ SQLUsageSession(self.server, self.port, user=user, pswd=pswd, root="/")
+ for user, pswd in itertools.izip(self.users, self.pswds)
+ ]
# Set of requests to execute
requests = [
- MultigetTest("multiget-1", session, joinURL(self.userhref, "calendar/"), self.logFilePath, 1),
- MultigetTest("multiget-50", session, joinURL(self.userhref, "calendar/"), self.logFilePath, 50),
- PropfindTest("propfind-cal", session, joinURL(self.userhref, "calendar/"), self.logFilePath, 1),
- SyncTest("sync-full", session, joinURL(self.userhref, "calendar/"), self.logFilePath, True, 0),
- SyncTest("sync-1", session, joinURL(self.userhref, "calendar/"), self.logFilePath, False, 1),
- QueryTest("query-1", session, joinURL(self.userhref, "calendar/"), self.logFilePath, 1),
- QueryTest("query-10", session, joinURL(self.userhref, "calendar/"), self.logFilePath, 10),
+ MultigetTest("multiget-1", sessions, self.logFilePath, 1),
+ MultigetTest("multiget-50", sessions, self.logFilePath, 50),
+ PropfindTest("propfind-cal", sessions, self.logFilePath, 1),
+ SyncTest("sync-full", sessions, self.logFilePath, True, 0),
+ SyncTest("sync-1", sessions, self.logFilePath, False, 1),
+ QueryTest("query-1", sessions, self.logFilePath, 1),
+ QueryTest("query-10", sessions, self.logFilePath, 10),
+ PutTest("put", sessions, self.logFilePath),
+ InviteTest("invite", sessions, self.logFilePath),
]
self.requestLabels = [request.label for request in requests]
# Warm-up server by doing calendar home and calendar propfinds
props = (davxml.resourcetype,)
- session.getPropertiesOnHierarchy(URL(path=self.userhref), props)
- session.getPropertiesOnHierarchy(URL(path=joinURL(self.userhref, "calendar/")), props)
+ for session in sessions:
+ session.getPropertiesOnHierarchy(URL(path=session.homeHref), props)
+ session.getPropertiesOnHierarchy(URL(path=session.calendarHref), props)
# Now loop over sets of events
- for count in EVENT_COUNTS:
+ for count in counts:
print "Testing count = %d" % (count,)
- self.ensureEvents(session, count)
+ self.ensureEvents(sessions[0], sessions[0].calendarHref, count)
result = {}
for request in requests:
+ print " Test = %s" % (request.label,)
result[request.label] = request.execute()
self.results[count] = result
@@ -121,26 +139,26 @@
self._printReport("SQL Statement Count", "count", "%d")
self._printReport("SQL Rows Returned", "rows", "%d")
- self._printReport("SQL Time", "timing", "%.3f")
+ self._printReport("SQL Time", "timing", "%.1f")
def _printReport(self, title, attr, colFormat):
table = tables.Table()
print title
- headers = ["Events"] + sorted([label for label in self.requestLabels])
+ headers = ["Events"] + self.requestLabels
table.addHeader(headers)
formats = [tables.Table.ColumnFormat("%d", tables.Table.ColumnFormat.RIGHT_JUSTIFY)] + \
[tables.Table.ColumnFormat(colFormat, tables.Table.ColumnFormat.RIGHT_JUSTIFY)] * len(self.requestLabels)
table.setDefaultColumnFormats(formats)
for k in sorted(self.results.keys()):
- row = [k] + [getattr(self.results[k][item], attr) for item in sorted(self.results[k].keys())]
+ row = [k] + [getattr(self.results[k][item], attr) for item in self.requestLabels]
table.addRow(row)
os = StringIO()
table.printTable(os=os)
print os.getvalue()
print
- def ensureEvents(self, session, n):
+ def ensureEvents(self, session, calendarhref, n):
"""
Make sure the required number of events are present in the calendar.
@@ -150,7 +168,7 @@
now = PyCalendarDateTime.getNowUTC()
for i in range(n - self.currentCount):
index = self.currentCount + i + 1
- href = joinURL(self.userhref, "calendar", "%d.ics" % (index,))
+ href = joinURL(calendarhref, "%d.ics" % (index,))
session.writeData(URL(path=href), ICAL % (now.getYear() + 1, index,), "text/calendar")
self.currentCount = n
@@ -166,6 +184,7 @@
--port Server port
--user User name
--pswd Password
+ --counts Comma-separated list of event counts to test
Arguments:
FILE File name for sqlstats.log to analyze.
@@ -183,11 +202,12 @@
server = "localhost"
port = 8008
- user = "user01"
- pswd = "user01"
+ users = ("user01", "user02",)
+ pswds = ("user01", "user02",)
file = "sqlstats.logs"
+ counts = EVENT_COUNTS
- options, args = getopt.getopt(sys.argv[1:], "h", ["server", "port", "user", "pswd",])
+ options, args = getopt.getopt(sys.argv[1:], "h", ["server=", "port=", "user=", "pswd=", "counts=",])
for option, value in options:
if option == "-h":
@@ -197,9 +217,11 @@
elif option == "--port":
port = int(value)
elif option == "--user":
- user = value
+ users = value.split(",")
elif option == "--pswd":
- pswd = value
+ pswds = value.split(",")
+ elif option == "--counts":
+ counts = [int(i) for i in value.split(",")]
else:
usage("Unrecognized option: %s" % (option,))
@@ -209,6 +231,6 @@
elif len(args) != 0:
usage("Must zero or one file arguments")
- sql = SQLUsage(server, port, user, pswd, file)
- sql.runLoop()
+ sql = SQLUsage(server, port, users, pswds, file)
+ sql.runLoop(counts)
sql.report()
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.macosforge.org/pipermail/calendarserver-changes/attachments/20120814/4f48d331/attachment-0001.html>
More information about the calendarserver-changes
mailing list