[CalendarServer-changes] [11385] CalendarServer/trunk

source_changes at macosforge.org source_changes at macosforge.org
Wed Jun 19 17:35:20 PDT 2013


Revision: 11385
          http://trac.calendarserver.org//changeset/11385
Author:   sagen at apple.com
Date:     2013-06-19 17:35:20 -0700 (Wed, 19 Jun 2013)
Log Message:
-----------
Adds process type "Agent" -- an on-demand AMP service spawned by launchd, meant for servicing configuration commands

Modified Paths:
--------------
    CalendarServer/trunk/bin/caldavd
    CalendarServer/trunk/calendarserver/tap/caldav.py
    CalendarServer/trunk/calendarserver/tools/gateway.py
    CalendarServer/trunk/contrib/launchd/calendarserver.plist
    CalendarServer/trunk/txdav/base/datastore/subpostgres.py

Added Paths:
-----------
    CalendarServer/trunk/calendarserver/tools/agent.py
    CalendarServer/trunk/contrib/launchd/agent.plist

Modified: CalendarServer/trunk/bin/caldavd
===================================================================
--- CalendarServer/trunk/bin/caldavd	2013-06-19 23:14:34 UTC (rev 11384)
+++ CalendarServer/trunk/bin/caldavd	2013-06-20 00:35:20 UTC (rev 11385)
@@ -31,7 +31,7 @@
 profile="";
 twistd_reactor="";
 child_reactor=""
-extra="-o FailIfUpgradeNeeded=False";
+extra="";
 
 py_version ()
 {

Modified: CalendarServer/trunk/calendarserver/tap/caldav.py
===================================================================
--- CalendarServer/trunk/calendarserver/tap/caldav.py	2013-06-19 23:14:34 UTC (rev 11384)
+++ CalendarServer/trunk/calendarserver/tap/caldav.py	2013-06-20 00:35:20 UTC (rev 11385)
@@ -105,6 +105,7 @@
 from calendarserver.push.notifier import PushDistributor
 from calendarserver.push.amppush import AMPPushMaster, AMPPushForwarder
 from calendarserver.push.applepush import ApplePushNotifierService
+from calendarserver.tools.agent import makeAgentService
 
 try:
     from calendarserver.version import version
@@ -388,9 +389,10 @@
 
         # Having CalDAV *and* CardDAV both disabled is an illegal configuration
         # for a running server (but is fine for command-line utilities)
-        if not config.EnableCalDAV and not config.EnableCardDAV:
-            print("Neither EnableCalDAV nor EnableCardDAV are set to True.")
-            sys.exit(1)
+        if config.ProcessType not in ["Agent", "Utility"]:
+            if not config.EnableCalDAV and not config.EnableCardDAV:
+                print("Neither EnableCalDAV nor EnableCardDAV are set to True.")
+                sys.exit(1)
 
         uid, gid = None, None
 
@@ -1234,6 +1236,22 @@
         return self.storageService(toolServiceCreator, None, uid=uid, gid=gid)
 
 
+    def makeService_Agent(self, options):
+        """
+        Create an agent service which listens for configuration requests
+        """
+
+        # Don't use memcached -- calendar server might take it away at any
+        # moment
+        config.Memcached.Pools.Default.ClientEnabled = False
+
+        def agentServiceCreator(pool, store, ignored):
+            return makeAgentService(store)
+
+        uid, gid = getSystemIDs(config.UserName, config.GroupName)
+        return self.storageService(agentServiceCreator, None, uid=uid, gid=gid)
+
+
     def storageService(self, createMainService, logObserver, uid=None, gid=None):
         """
         If necessary, create a service to be started used for storage; for

Added: CalendarServer/trunk/calendarserver/tools/agent.py
===================================================================
--- CalendarServer/trunk/calendarserver/tools/agent.py	                        (rev 0)
+++ CalendarServer/trunk/calendarserver/tools/agent.py	2013-06-20 00:35:20 UTC (rev 11385)
@@ -0,0 +1,180 @@
+#!/usr/bin/env python
+
+##
+# Copyright (c) 2013 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 __future__ import print_function
+
+import cStringIO
+import socket
+from twext.python.plistlib import readPlistFromString, writePlistToString
+from calendarserver.tap.util import getRootResource
+from twisted.application.internet import StreamServerEndpointService
+from twisted.internet.endpoints import AdoptedStreamServerEndpoint
+from twisted.protocols import amp
+from twisted.internet.protocol import Factory
+from twisted.internet.defer import inlineCallbacks, returnValue
+
+# TODO, implement this:
+# from launchd import getLaunchdSocketFds
+
+# For the sample client, below:
+from twisted.internet import reactor
+from twisted.internet.protocol import ClientCreator
+
+from twext.python.log import Logger
+log = Logger()
+
+"""
+A service spawned on-demand by launchd, meant to handle configuration requests
+from Server.app.  When a request comes in on the socket specified in the launchd
+agent.plist, launchd will run "caldavd -t Agent" which ends up creating this
+service.  AMP protocol commands sent to this socket are passed to gateway.Runner.
+"""
+
+class GatewayAMPCommand(amp.Command):
+    """
+    A command to be executed by gateway.Runner 
+    """
+    arguments = [('command', amp.String())]
+    response = [('result', amp.String())]
+
+
+class GatewayAMPProtocol(amp.AMP):
+    """
+    Passes commands to gateway.Runner and returns the results
+    """
+
+    def __init__(self, store, davRootResource, directory):
+        """
+        @param store: an already opened store
+        @param davRootResource: the root resource, required for principal
+            operations
+        @param directory: a directory service
+        """
+        amp.AMP.__init__(self)
+        self.store = store
+        self.davRootResource = davRootResource
+        self.directory = directory
+
+
+    @GatewayAMPCommand.responder
+    @inlineCallbacks
+    def gatewayCommandReceived(self, command):
+        """
+        Process a command via gateway.Runner
+
+        @param command: GatewayAMPCommand
+        @returns: a deferred returning a dict
+        """
+        command = readPlistFromString(command)
+        output = cStringIO.StringIO()
+        from calendarserver.tools.gateway import Runner
+        runner = Runner(self.davRootResource, self.directory, self.store,
+            [command], output=output)
+
+        try:
+            yield runner.run()
+            result = output.getvalue()
+            output.close()
+        except Exception as e:
+            error = {
+                "Error" : str(e),
+            }
+            result = writePlistToString(error)
+
+        output.close()
+        returnValue(dict(result=result))
+
+
+class GatewayAMPFactory(Factory):
+    """
+    Builds GatewayAMPProtocols
+    """
+    protocol = GatewayAMPProtocol
+
+    def __init__(self, store):
+        """
+        @param store: an already opened store
+        """
+        self.store = store
+        from twistedcaldav.config import config
+        self.davRootResource = getRootResource(config, self.store)
+        self.directory = self.davRootResource.getDirectory()
+
+    def buildProtocol(self, addr):
+        return GatewayAMPProtocol(self.store, self.davRootResource,
+            self.directory)
+
+
+def makeAgentService(store):
+    """
+    Returns a service which will process GatewayAMPCommands, using a socket
+    file descripter acquired by launchd
+
+    @param store: an already opened store
+    @returns: service
+    """
+    from twisted.internet import reactor
+
+    # TODO: remove this
+    def getLaunchdSocketFds():
+        return {}
+
+    # TODO: implement this
+    sockets = getLaunchdSocketFds() 
+    fd = sockets["AgentSocket"][0]
+    
+    # TODO: use UNIX socket
+    family = socket.AF_INET
+    endpoint = AdoptedStreamServerEndpoint(reactor, fd, family)
+    return StreamServerEndpointService(endpoint, GatewayAMPFactory(store))
+
+
+
+#
+# A test client
+#
+
+command = """<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+        <key>command</key>
+        <string>getLocationAndResourceList</string>
+</dict>
+</plist>"""
+
+def getList():
+    creator = ClientCreator(reactor, amp.AMP)
+    # TODO: use UNIX socket
+    d = creator.connectTCP('127.0.0.1', 12345)
+
+    def connected(ampProto):
+        return ampProto.callRemote(GatewayAMPCommand, command=command)
+    d.addCallback(connected)
+
+    def resulted(result):
+        return result['result']
+    d.addCallback(resulted)
+
+    def done(result):
+        print('Done: %s' % (result,))
+        reactor.stop()
+    d.addCallback(done)
+
+if __name__ == '__main__':
+    getList()
+    reactor.run()


Property changes on: CalendarServer/trunk/calendarserver/tools/agent.py
___________________________________________________________________
Added: svn:executable
   + *

Modified: CalendarServer/trunk/calendarserver/tools/gateway.py
===================================================================
--- CalendarServer/trunk/calendarserver/tools/gateway.py	2013-06-19 23:14:34 UTC (rev 11384)
+++ CalendarServer/trunk/calendarserver/tools/gateway.py	2013-06-20 00:35:20 UTC (rev 11385)
@@ -159,23 +159,26 @@
 
 class Runner(object):
 
-    def __init__(self, root, directory, store, commands):
+    def __init__(self, root, directory, store, commands, output=None):
         self.root = root
         self.dir = directory
         self.store = store
         self.commands = commands
+        if output is None:
+            output = sys.stdout
+        self.output = output
 
 
     def validate(self):
         # Make sure commands are valid
         for command in self.commands:
             if 'command' not in command:
-                respondWithError("'command' missing from plist")
+                self.respondWithError("'command' missing from plist")
                 return False
             commandName = command['command']
             methodName = "command_%s" % (commandName,)
             if not hasattr(self, methodName):
-                respondWithError("Unknown command '%s'" % (commandName,))
+                self.respondWithError("Unknown command '%s'" % (commandName,))
                 return False
         return True
 
@@ -189,17 +192,17 @@
                 if hasattr(self, methodName):
                     (yield getattr(self, methodName)(command))
                 else:
-                    respondWithError("Unknown command '%s'" % (commandName,))
+                    self.respondWithError("Unknown command '%s'" % (commandName,))
 
         except Exception, e:
-            respondWithError("Command failed: '%s'" % (str(e),))
+            self.respondWithError("Command failed: '%s'" % (str(e),))
             raise
 
     # Locations
 
 
     def command_getLocationList(self, command):
-        respondWithRecordsOfTypes(self.dir, command, ["locations"])
+        self.respondWithRecordsOfTypes(self.dir, command, ["locations"])
 
 
     @inlineCallbacks
@@ -212,7 +215,7 @@
         try:
             record = (yield updateRecord(True, self.dir, "locations", **kwargs))
         except DirectoryError, e:
-            respondWithError(str(e))
+            self.respondWithError(str(e))
             return
 
         readProxies = command.get("ReadProxies", None)
@@ -220,7 +223,7 @@
         principal = principalForPrincipalID(record.guid, directory=self.dir)
         (yield setProxies(self.store, principal, readProxies, writeProxies, directory=self.dir))
 
-        respondWithRecordsOfTypes(self.dir, command, ["locations"])
+        self.respondWithRecordsOfTypes(self.dir, command, ["locations"])
 
 
     @inlineCallbacks
@@ -228,18 +231,18 @@
         guid = command['GeneratedUID']
         record = self.dir.recordWithGUID(guid)
         if record is None:
-            respondWithError("Principal not found: %s" % (guid,))
+            self.respondWithError("Principal not found: %s" % (guid,))
             return
         recordDict = recordToDict(record)
         principal = principalForPrincipalID(guid, directory=self.dir)
         if principal is None:
-            respondWithError("Principal not found: %s" % (guid,))
+            self.respondWithError("Principal not found: %s" % (guid,))
             return
         recordDict['AutoSchedule'] = principal.getAutoSchedule()
         recordDict['AutoAcceptGroup'] = principal.getAutoAcceptGroup()
         recordDict['ReadProxies'], recordDict['WriteProxies'] = (yield getProxies(principal,
             directory=self.dir))
-        respond(command, recordDict)
+        self.respond(command, recordDict)
 
     command_getResourceAttributes = command_getLocationAttributes
 
@@ -260,7 +263,7 @@
         try:
             record = (yield updateRecord(False, self.dir, "locations", **kwargs))
         except DirectoryError, e:
-            respondWithError(str(e))
+            self.respondWithError(str(e))
             return
 
         readProxies = command.get("ReadProxies", None)
@@ -279,15 +282,15 @@
         try:
             self.dir.destroyRecord("locations", **kwargs)
         except DirectoryError, e:
-            respondWithError(str(e))
+            self.respondWithError(str(e))
             return
-        respondWithRecordsOfTypes(self.dir, command, ["locations"])
+        self.respondWithRecordsOfTypes(self.dir, command, ["locations"])
 
     # Resources
 
 
     def command_getResourceList(self, command):
-        respondWithRecordsOfTypes(self.dir, command, ["resources"])
+        self.respondWithRecordsOfTypes(self.dir, command, ["resources"])
 
 
     @inlineCallbacks
@@ -300,7 +303,7 @@
         try:
             record = (yield updateRecord(True, self.dir, "resources", **kwargs))
         except DirectoryError, e:
-            respondWithError(str(e))
+            self.respondWithError(str(e))
             return
 
         readProxies = command.get("ReadProxies", None)
@@ -308,7 +311,7 @@
         principal = principalForPrincipalID(record.guid, directory=self.dir)
         (yield setProxies(self.store, principal, readProxies, writeProxies, directory=self.dir))
 
-        respondWithRecordsOfTypes(self.dir, command, ["resources"])
+        self.respondWithRecordsOfTypes(self.dir, command, ["resources"])
 
 
     @inlineCallbacks
@@ -328,7 +331,7 @@
         try:
             record = (yield updateRecord(False, self.dir, "resources", **kwargs))
         except DirectoryError, e:
-            respondWithError(str(e))
+            self.respondWithError(str(e))
             return
 
         readProxies = command.get("ReadProxies", None)
@@ -347,13 +350,13 @@
         try:
             self.dir.destroyRecord("resources", **kwargs)
         except DirectoryError, e:
-            respondWithError(str(e))
+            self.respondWithError(str(e))
             return
-        respondWithRecordsOfTypes(self.dir, command, ["resources"])
+        self.respondWithRecordsOfTypes(self.dir, command, ["resources"])
 
         
     def command_getLocationAndResourceList(self, command):
-        respondWithRecordsOfTypes(self.dir, command, ["locations", "resources"])
+        self.respondWithRecordsOfTypes(self.dir, command, ["locations", "resources"])
 
 
     # Proxies
@@ -363,9 +366,9 @@
     def command_listWriteProxies(self, command):
         principal = principalForPrincipalID(command['Principal'], directory=self.dir)
         if principal is None:
-            respondWithError("Principal not found: %s" % (command['Principal'],))
+            self.respondWithError("Principal not found: %s" % (command['Principal'],))
             return
-        (yield respondWithProxies(self.dir, command, principal, "write"))
+        (yield self.respondWithProxies(self.dir, command, principal, "write"))
 
 
     @inlineCallbacks
@@ -373,90 +376,90 @@
         principal = principalForPrincipalID(command['Principal'],
             directory=self.dir)
         if principal is None:
-            respondWithError("Principal not found: %s" % (command['Principal'],))
+            self.respondWithError("Principal not found: %s" % (command['Principal'],))
             return
 
         proxy = principalForPrincipalID(command['Proxy'], directory=self.dir)
         if proxy is None:
-            respondWithError("Proxy not found: %s" % (command['Proxy'],))
+            self.respondWithError("Proxy not found: %s" % (command['Proxy'],))
             return
         try:
             (yield addProxy(self.root, self.dir, self.store, principal, "write", proxy))
         except ProxyError, e:
-            respondWithError(str(e))
+            self.respondWithError(str(e))
             return
         except ProxyWarning, e:
             pass
-        (yield respondWithProxies(self.dir, command, principal, "write"))
+        (yield self.respondWithProxies(self.dir, command, principal, "write"))
 
 
     @inlineCallbacks
     def command_removeWriteProxy(self, command):
         principal = principalForPrincipalID(command['Principal'], directory=self.dir)
         if principal is None:
-            respondWithError("Principal not found: %s" % (command['Principal'],))
+            self.respondWithError("Principal not found: %s" % (command['Principal'],))
             return
         proxy = principalForPrincipalID(command['Proxy'], directory=self.dir)
         if proxy is None:
-            respondWithError("Proxy not found: %s" % (command['Proxy'],))
+            self.respondWithError("Proxy not found: %s" % (command['Proxy'],))
             return
         try:
             (yield removeProxy(self.root, self.dir, self.store, principal, proxy, proxyTypes=("write",)))
         except ProxyError, e:
-            respondWithError(str(e))
+            self.respondWithError(str(e))
             return
         except ProxyWarning, e:
             pass
-        (yield respondWithProxies(self.dir, command, principal, "write"))
+        (yield self.respondWithProxies(self.dir, command, principal, "write"))
 
 
     @inlineCallbacks
     def command_listReadProxies(self, command):
         principal = principalForPrincipalID(command['Principal'], directory=self.dir)
         if principal is None:
-            respondWithError("Principal not found: %s" % (command['Principal'],))
+            self.respondWithError("Principal not found: %s" % (command['Principal'],))
             return
-        (yield respondWithProxies(self.dir, command, principal, "read"))
+        (yield self.respondWithProxies(self.dir, command, principal, "read"))
 
 
     @inlineCallbacks
     def command_addReadProxy(self, command):
         principal = principalForPrincipalID(command['Principal'], directory=self.dir)
         if principal is None:
-            respondWithError("Principal not found: %s" % (command['Principal'],))
+            self.respondWithError("Principal not found: %s" % (command['Principal'],))
             return
         proxy = principalForPrincipalID(command['Proxy'], directory=self.dir)
         if proxy is None:
-            respondWithError("Proxy not found: %s" % (command['Proxy'],))
+            self.respondWithError("Proxy not found: %s" % (command['Proxy'],))
             return
         try:
             (yield addProxy(self.root, self.dir, self.store, principal, "read", proxy))
         except ProxyError, e:
-            respondWithError(str(e))
+            self.respondWithError(str(e))
             return
         except ProxyWarning, e:
             pass
-        (yield respondWithProxies(self.dir, command, principal, "read"))
+        (yield self.respondWithProxies(self.dir, command, principal, "read"))
 
 
     @inlineCallbacks
     def command_removeReadProxy(self, command):
         principal = principalForPrincipalID(command['Principal'], directory=self.dir)
         if principal is None:
-            respondWithError("Principal not found: %s" % (command['Principal'],))
+            self.respondWithError("Principal not found: %s" % (command['Principal'],))
             return
         proxy = principalForPrincipalID(command['Proxy'], directory=self.dir)
         if proxy is None:
-            respondWithError("Proxy not found: %s" % (command['Proxy'],))
+            self.respondWithError("Proxy not found: %s" % (command['Proxy'],))
             return
         try:
             (yield removeProxy(self.root, self.dir, self.store, principal, proxy, proxyTypes=("read",)))
         except ProxyError, e:
-            respondWithError(str(e))
+            self.respondWithError(str(e))
             return
         except ProxyWarning, e:
             pass
-        (yield respondWithProxies(self.dir, command, principal, "read"))
+        (yield self.respondWithProxies(self.dir, command, principal, "read"))
 
 
     @inlineCallbacks
@@ -473,27 +476,45 @@
         cutoff.setDateOnly(False)
         cutoff.offsetDay(-retainDays)
         eventCount = (yield PurgeOldEventsService.purgeOldEvents(self.store, cutoff, DEFAULT_BATCH_SIZE))
-        respond(command, {'EventsRemoved' : eventCount, "RetainDays" : retainDays})
+        self.respond(command, {'EventsRemoved' : eventCount, "RetainDays" : retainDays})
 
 
 
- at inlineCallbacks
-def respondWithProxies(directory, command, principal, proxyType):
-    proxies = []
-    subPrincipal = proxySubprincipal(principal, proxyType)
-    if subPrincipal is not None:
-        membersProperty = (yield subPrincipal.readProperty(davxml.GroupMemberSet, None))
-        if membersProperty.children:
-            for member in membersProperty.children:
-                proxyPrincipal = principalForPrincipalID(str(member), directory=directory)
-                proxies.append(proxyPrincipal.record.guid)
+    @inlineCallbacks
+    def respondWithProxies(self, directory, command, principal, proxyType):
+        proxies = []
+        subPrincipal = proxySubprincipal(principal, proxyType)
+        if subPrincipal is not None:
+            membersProperty = (yield subPrincipal.readProperty(davxml.GroupMemberSet, None))
+            if membersProperty.children:
+                for member in membersProperty.children:
+                    proxyPrincipal = principalForPrincipalID(str(member), directory=directory)
+                    proxies.append(proxyPrincipal.record.guid)
 
-    respond(command, {
-        'Principal' : principal.record.guid, 'Proxies' : proxies
-    })
+        self.respond(command, {
+            'Principal' : principal.record.guid, 'Proxies' : proxies
+        })
 
 
 
+    def respondWithRecordsOfTypes(self, directory, command, recordTypes):
+        result = []
+        for recordType in recordTypes:
+            for record in directory.listRecords(recordType):
+                recordDict = recordToDict(record)
+                result.append(recordDict)
+        self.respond(command, result)
+
+
+
+    def respond(self, command, result):
+        self.output.write(writePlistToString({'command' : command['command'], 'result' : result}))
+
+
+    def respondWithError(self, msg, status=1):
+        self.output.write(writePlistToString({'error' : msg, }))
+
+
 def recordToDict(record):
     recordDict = {}
     for key, info in attrMap.iteritems():
@@ -509,26 +530,10 @@
             pass
     return recordDict
 
-
-
-def respondWithRecordsOfTypes(directory, command, recordTypes):
-    result = []
-    for recordType in recordTypes:
-        for record in directory.listRecords(recordType):
-            recordDict = recordToDict(record)
-            result.append(recordDict)
-    respond(command, result)
-
-
-
-def respond(command, result):
-    sys.stdout.write(writePlistToString({'command' : command['command'], 'result' : result}))
-
-
-
 def respondWithError(msg, status=1):
     sys.stdout.write(writePlistToString({'error' : msg, }))
 
 
+
 if __name__ == "__main__":
     main()

Added: CalendarServer/trunk/contrib/launchd/agent.plist
===================================================================
--- CalendarServer/trunk/contrib/launchd/agent.plist	                        (rev 0)
+++ CalendarServer/trunk/contrib/launchd/agent.plist	2013-06-20 00:35:20 UTC (rev 11385)
@@ -0,0 +1,86 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<!--
+Copyright (c) 2013 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.
+ -->
+
+<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+
+  <key>Label</key>
+  <string>org.calendarserver.agent</string>
+
+  <key>ProgramArguments</key>
+  <array>
+    <string>/Applications/Server.app/Contents/ServerRoot/usr/sbin/caldavd</string>
+    <string>-t</string>
+    <string>Agent</string>
+    <string>-X</string>
+    <string>-R</string>
+    <string>caldav_kqueue</string>
+    <string>-o</string>
+    <string>PIDFile=agent.pid</string>
+  </array>
+
+  <key>InitGroups</key>
+  <true/>
+
+  <key>ServiceIPC</key>
+  <true/>
+
+  <key>Sockets</key>
+  <dict>
+          <key>AgentSocket</key>
+          <dict>
+                  <key>SockServiceName</key>
+                  <string>12345</string>
+                  <key>SockFamily</key>
+                  <string>IPv4</string>
+          </dict>
+          <!--
+          <key>AgentSocket</key>
+          <dict>
+                  <key>SockFamily</key>
+                  <string>UNIX</string>
+                  <key>SockPathName</key>
+                  <string>/some/path/org.calendarserver.agent.sock</string>
+          </dict>
+          -->
+  </dict>
+
+  <key>HardResourceLimits</key>
+  <dict>
+    <key>NumberOfFiles</key>
+    <integer>12000</integer>
+  </dict>
+
+  <key>SoftResourceLimits</key>
+  <dict>
+    <key>NumberOfFiles</key>
+    <integer>12000</integer>
+  </dict>
+
+  <key>PreventsSleep</key>
+  <true/>
+
+  <key>StandardOutPath</key>
+  <string>/var/log/caldavd/agent.log</string>
+
+  <key>StandardErrorPath</key>
+  <string>/var/log/caldavd/agent.log</string>
+
+</dict>
+</plist>

Modified: CalendarServer/trunk/contrib/launchd/calendarserver.plist
===================================================================
--- CalendarServer/trunk/contrib/launchd/calendarserver.plist	2013-06-19 23:14:34 UTC (rev 11384)
+++ CalendarServer/trunk/contrib/launchd/calendarserver.plist	2013-06-20 00:35:20 UTC (rev 11385)
@@ -32,6 +32,8 @@
     <string>-X</string>
     <string>-R</string>
     <string>caldav_kqueue</string>
+    <string>-o</string>
+    <string>FailIfUpgradeNeeded=False</string>
   </array>
 
   <key>InitGroups</key>

Modified: CalendarServer/trunk/txdav/base/datastore/subpostgres.py
===================================================================
--- CalendarServer/trunk/txdav/base/datastore/subpostgres.py	2013-06-19 23:14:34 UTC (rev 11384)
+++ CalendarServer/trunk/txdav/base/datastore/subpostgres.py	2013-06-20 00:35:20 UTC (rev 11385)
@@ -402,6 +402,7 @@
             createDatabaseCursor.execute("commit")
             return createDatabaseConn, createDatabaseCursor
 
+        # TODO: always go through pg_ctl start
         try:
             createDatabaseConn, createDatabaseCursor = createConnection()
         except pgdb.DatabaseError:
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.macosforge.org/pipermail/calendarserver-changes/attachments/20130619/9cc06cd6/attachment-0001.html>


More information about the calendarserver-changes mailing list