[CalendarServer-changes] [7328] CalendarServer/branches/users/wsanchez/deployment

source_changes at macosforge.org source_changes at macosforge.org
Tue Apr 19 19:17:46 PDT 2011


Revision: 7328
          http://trac.macosforge.org/projects/calendarserver/changeset/7328
Author:   cdaboo at apple.com
Date:     2011-04-19 19:17:46 -0700 (Tue, 19 Apr 2011)
Log Message:
-----------
Make server-to-server work.

Modified Paths:
--------------
    CalendarServer/branches/users/wsanchez/deployment/calendarserver/tools/manageaugments.py
    CalendarServer/branches/users/wsanchez/deployment/conf/augments.dtd
    CalendarServer/branches/users/wsanchez/deployment/twistedcaldav/config.py
    CalendarServer/branches/users/wsanchez/deployment/twistedcaldav/directory/augment.py
    CalendarServer/branches/users/wsanchez/deployment/twistedcaldav/directory/directory.py
    CalendarServer/branches/users/wsanchez/deployment/twistedcaldav/directory/idirectory.py
    CalendarServer/branches/users/wsanchez/deployment/twistedcaldav/directory/principal.py
    CalendarServer/branches/users/wsanchez/deployment/twistedcaldav/directory/test/augments-test-default.xml
    CalendarServer/branches/users/wsanchez/deployment/twistedcaldav/directory/test/augments-test.xml
    CalendarServer/branches/users/wsanchez/deployment/twistedcaldav/directory/test/test_augment.py
    CalendarServer/branches/users/wsanchez/deployment/twistedcaldav/directory/xmlaugmentsparser.py
    CalendarServer/branches/users/wsanchez/deployment/twistedcaldav/method/report_common.py
    CalendarServer/branches/users/wsanchez/deployment/twistedcaldav/query/expression.py
    CalendarServer/branches/users/wsanchez/deployment/twistedcaldav/scheduling/addressmapping.py
    CalendarServer/branches/users/wsanchez/deployment/twistedcaldav/scheduling/caldav.py
    CalendarServer/branches/users/wsanchez/deployment/twistedcaldav/scheduling/cuaddress.py
    CalendarServer/branches/users/wsanchez/deployment/twistedcaldav/scheduling/ischedule.py
    CalendarServer/branches/users/wsanchez/deployment/twistedcaldav/scheduling/itip.py
    CalendarServer/branches/users/wsanchez/deployment/twistedcaldav/scheduling/scheduler.py
    CalendarServer/branches/users/wsanchez/deployment/twistedcaldav/static.py
    CalendarServer/branches/users/wsanchez/deployment/twistedcaldav/test/test_tap.py

Added Paths:
-----------
    CalendarServer/branches/users/wsanchez/deployment/conf/servers-test.xml
    CalendarServer/branches/users/wsanchez/deployment/conf/servers.dtd
    CalendarServer/branches/users/wsanchez/deployment/conf/servers.xml
    CalendarServer/branches/users/wsanchez/deployment/twistedcaldav/servers.py
    CalendarServer/branches/users/wsanchez/deployment/twistedcaldav/xmlutil.py

Removed Paths:
-------------
    CalendarServer/branches/users/wsanchez/deployment/conf/partitions-test.plist
    CalendarServer/branches/users/wsanchez/deployment/conf/partitions.plist
    CalendarServer/branches/users/wsanchez/deployment/twistedcaldav/partitions.py

Property Changed:
----------------
    CalendarServer/branches/users/wsanchez/deployment/


Property changes on: CalendarServer/branches/users/wsanchez/deployment
___________________________________________________________________
Modified: svn:mergeinfo
   - /CalendarServer/branches/users/cdaboo/deployment-partition-4524:4525-4594
/CalendarServer/branches/users/cdaboo/deployment-partition-4593:4594-4723
/CalendarServer/branches/users/cdaboo/deployment-partition-4722:4723-5830
/CalendarServer/branches/users/glyph/deployment-plus-sendfd:5426-5429
/CalendarServer/branches/users/sagen/deployment-inherit-fds-4571:4573-4709
/CalendarServer/branches/users/sagen/deployment-inspection:4927-4937
/CalendarServer/trunk:3189,3861,3941
   + /CalendarServer/branches/users/cdaboo/deployment-partition-4524:4525-4594
/CalendarServer/branches/users/cdaboo/deployment-partition-4593:4594-4723
/CalendarServer/branches/users/cdaboo/deployment-partition-4722:4723-5830
/CalendarServer/branches/users/glyph/deployment-plus-sendfd:5426-5429
/CalendarServer/branches/users/sagen/deployment-inherit-fds-4571:4573-4709
/CalendarServer/branches/users/sagen/deployment-inspection:4927-4937
/CalendarServer/branches/users/wsanchez/deployment-pre-partition:5860
/CalendarServer/trunk:3189,3861,3941

Modified: CalendarServer/branches/users/wsanchez/deployment/calendarserver/tools/manageaugments.py
===================================================================
--- CalendarServer/branches/users/wsanchez/deployment/calendarserver/tools/manageaugments.py	2011-04-19 22:23:15 UTC (rev 7327)
+++ CalendarServer/branches/users/wsanchez/deployment/calendarserver/tools/manageaugments.py	2011-04-20 02:17:46 UTC (rev 7328)
@@ -1,6 +1,6 @@
 #!/usr/bin/env python
 ##
-# Copyright (c) 2009-2010 Apple Inc. All rights reserved.
+# Copyright (c) 2009-2011 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.
@@ -63,7 +63,7 @@
     child = parent.find(tag)
     child.text = text
 
-def doAdd(xmlfile, uid, host, enable_calendar, auto_schedule):
+def doAdd(xmlfile, uid, serverID, partitionID, enable_calendar, auto_schedule):
 
     augments_node = readXML(xmlfile)
 
@@ -84,7 +84,8 @@
     record = addSubElement(augments_node, xmlaugmentsparser.ELEMENT_RECORD, "\n    ")
     addSubElement(record, xmlaugmentsparser.ELEMENT_UID, uid, 4)
     addSubElement(record, xmlaugmentsparser.ELEMENT_ENABLE, "true", 4)
-    addSubElement(record, xmlaugmentsparser.ELEMENT_HOSTEDAT, host, 4)
+    addSubElement(record, xmlaugmentsparser.ELEMENT_SERVERID, serverID, 4)
+    addSubElement(record, xmlaugmentsparser.ELEMENT_PARTITIONID, partitionID, 4)
     addSubElement(record, xmlaugmentsparser.ELEMENT_ENABLECALENDAR, "true" if enable_calendar else "false", 4)
     addSubElement(record, xmlaugmentsparser.ELEMENT_AUTOSCHEDULE, "true" if auto_schedule else "false", 2)
     
@@ -92,7 +93,7 @@
     writeXML(xmlfile, augments_node)
     print "Added uid '%s' in augment file: '%s'" % (uid, xmlfile,)
     
-def doModify(xmlfile, uid, host, enable_calendar, auto_schedule):
+def doModify(xmlfile, uid, serverID, partitionID, enable_calendar, auto_schedule):
 
     augments_node = readXML(xmlfile)
 
@@ -113,8 +114,10 @@
         error("Cannot modify uid '%s' because it does not exist in augment file: '%s'" % (uid, xmlfile,))
     
     # Modify record
-    if host is not None:
-        child.find(xmlaugmentsparser.ELEMENT_HOSTEDAT).text = host
+    if serverID is not None:
+        child.find(xmlaugmentsparser.ELEMENT_SERVERID).text = serverID
+    if partitionID is not None:
+        child.find(xmlaugmentsparser.ELEMENT_PARTITIONID).text = partitionID
     child.find(xmlaugmentsparser.ELEMENT_ENABLECALENDAR).text = "true" if enable_calendar else "false"
     child.find(xmlaugmentsparser.ELEMENT_AUTOSCHEDULE).text = "true" if auto_schedule else "false"
     
@@ -177,11 +180,13 @@
                       help="UID to manipulate", metavar="UID")
     parser.add_option("-i", "--uidfile", dest="uidfile",
                       help="File containing a list of UIDs to manipulate", metavar="UIDFILE")
-    parser.add_option("-n", "--node", dest="node",
-                      help="Partition node to assign to UID", metavar="NODE")
+    parser.add_option("-s", "--server", dest="serverID",
+                      help="Server id to assign to UID", metavar="SERVER")
+    parser.add_option("-p", "--partition", dest="partitionID",
+                      help="Partition id to assign to UID", metavar="PARTITION")
     parser.add_option("-c", "--enable-calendar", action="store_true", dest="enable_calendar",
                       default=True, help="Enable calendaring for this UID: %default")
-    parser.add_option("-a", "--auto-schedule", action="store_true", dest="auto_schedule",
+    parser.add_option("-x", "--auto-schedule", action="store_true", dest="auto_schedule",
                       default=False, help="Enable auto-schedule for this UID: %default")
 
     (options, args) = parser.parse_args()
@@ -200,13 +205,11 @@
                 uids.append(line[:-1])
         
     if args[0] == "add":
-        if not options.node:
-            parser.error("Partition node must be specified when adding")
         for uid in uids:
-            doAdd(options.xmlfilename, uid, options.node, options.enable_calendar, options.auto_schedule)
+            doAdd(options.xmlfilename, uid, options.serverID, options.partitionID, options.enable_calendar, options.auto_schedule)
     elif args[0] == "modify":
         for uid in uids:
-            doModify(options.xmlfilename, uid, options.node, options.enable_calendar, options.auto_schedule)
+            doModify(options.xmlfilename, uid, options.serverID, options.partitionID, options.enable_calendar, options.auto_schedule)
     elif args[0] == "remove":
         for uid in uids:
             doRemove(options.xmlfilename, uid)

Modified: CalendarServer/branches/users/wsanchez/deployment/conf/augments.dtd
===================================================================
--- CalendarServer/branches/users/wsanchez/deployment/conf/augments.dtd	2011-04-19 22:23:15 UTC (rev 7327)
+++ CalendarServer/branches/users/wsanchez/deployment/conf/augments.dtd	2011-04-20 02:17:46 UTC (rev 7328)
@@ -16,12 +16,13 @@
 
 <!ELEMENT augments (record*) >
 
-  <!ELEMENT record (uid, enable, hosted-at?, enable-calendar?, auto-schedule?)>
+  <!ELEMENT record (uid, enable, (server-id, partition-id?)?, enable-calendar?, auto-schedule?)>
     <!ATTLIST record repeat CDATA "1">
 
   <!ELEMENT uid               (#PCDATA)>
   <!ELEMENT enable            (#PCDATA)>
-  <!ELEMENT hosted-at         (#PCDATA)>
+  <!ELEMENT server-id          (#PCDATA)>
+  <!ELEMENT partition-id       (#PCDATA)>
   <!ELEMENT enable-calendar   (#PCDATA)>
   <!ELEMENT auto-schedule     (#PCDATA)>
 >

Deleted: CalendarServer/branches/users/wsanchez/deployment/conf/partitions-test.plist
===================================================================
--- CalendarServer/branches/users/wsanchez/deployment/conf/partitions-test.plist	2011-04-19 22:23:15 UTC (rev 7327)
+++ CalendarServer/branches/users/wsanchez/deployment/conf/partitions-test.plist	2011-04-20 02:17:46 UTC (rev 7328)
@@ -1,38 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-
-<!--
-    Copyright (c) 2009 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>partitions</key>
-  <array>
-    <dict>
-      <key>uid</key>
-      <string>00001</string>
-      <key>url</key>
-      <string>http://localhost:8008</string>
-    </dict>
-    <dict>
-      <key>uid</key>
-      <string>00002</string>
-      <key>url</key>
-      <string>http://localhost:8108</string>
-    </dict>
-  </array>
-</dict>
-</plist>

Deleted: CalendarServer/branches/users/wsanchez/deployment/conf/partitions.plist
===================================================================
--- CalendarServer/branches/users/wsanchez/deployment/conf/partitions.plist	2011-04-19 22:23:15 UTC (rev 7327)
+++ CalendarServer/branches/users/wsanchez/deployment/conf/partitions.plist	2011-04-20 02:17:46 UTC (rev 7328)
@@ -1,34 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-
-<!--
-    Copyright (c) 2009 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>partitions</key>
-  <array>
-  <!--
-    <dict>
-      <key>uid</key>
-      <string>00001</string>
-      <key>url</key>
-      <string>http://localhost:8008</string>
-    </dict>
-  -->
-  </array>
-</dict>
-</plist>

Added: CalendarServer/branches/users/wsanchez/deployment/conf/servers-test.xml
===================================================================
--- CalendarServer/branches/users/wsanchez/deployment/conf/servers-test.xml	                        (rev 0)
+++ CalendarServer/branches/users/wsanchez/deployment/conf/servers-test.xml	2011-04-20 02:17:46 UTC (rev 7328)
@@ -0,0 +1,36 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!--
+Copyright (c) 2011 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 servers SYSTEM "servers.dtd">
+
+<servers>
+  <server>
+    <id>00001</id>
+    <uri>http://localhost:8008</uri>
+    <partitions>
+    	<partition>
+    		<id>00001</id>
+    		<uri>http://localhost:8008</uri>
+    	</partition>
+    	<partition>
+    		<id>00002</id>
+    		<uri>http://localhost:8108</uri>
+    	</partition>
+    </partitions>
+  </server>
+</servers>

Added: CalendarServer/branches/users/wsanchez/deployment/conf/servers.dtd
===================================================================
--- CalendarServer/branches/users/wsanchez/deployment/conf/servers.dtd	                        (rev 0)
+++ CalendarServer/branches/users/wsanchez/deployment/conf/servers.dtd	2011-04-20 02:17:46 UTC (rev 7328)
@@ -0,0 +1,26 @@
+<!--
+Copyright (c) 2011 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.
+-->
+
+<!ELEMENT servers (server*) >
+
+	<!ELEMENT server (id, uri, partitions?) >
+		<!ATTLIST server implicit (yes|no) "yes">
+
+		<!ELEMENT id  (#PCDATA) >
+		<!ELEMENT uri (#PCDATA) >
+
+		<!ELEMENT partitions (partition*) >
+			<!ELEMENT partition (id, uri) >

Added: CalendarServer/branches/users/wsanchez/deployment/conf/servers.xml
===================================================================
--- CalendarServer/branches/users/wsanchez/deployment/conf/servers.xml	                        (rev 0)
+++ CalendarServer/branches/users/wsanchez/deployment/conf/servers.xml	2011-04-20 02:17:46 UTC (rev 7328)
@@ -0,0 +1,42 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!--
+Copyright (c) 2011 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 servers SYSTEM "servers.dtd">
+
+<servers>
+  <!--
+  <server>
+    <id>A</id>
+    <uri>https://caldav1.example.com:8843</uri>
+  </server>
+  <server>
+    <id>B</id>
+    <uri>https://caldav2.example.com:8843</uri>
+    <partitions>
+    	<partition>
+    		<id>00001</id>
+    		<url>https://machine1.example.com:8443</url>
+    	</partition>
+    	<partition>
+    		<id>00002</id>
+    		<url>https://machine2.example.com:8443</url>
+    	</partition>
+    </partitions>
+  </server>
+  -->
+</servers>

Modified: CalendarServer/branches/users/wsanchez/deployment/twistedcaldav/config.py
===================================================================
--- CalendarServer/branches/users/wsanchez/deployment/twistedcaldav/config.py	2011-04-19 22:23:15 UTC (rev 7327)
+++ CalendarServer/branches/users/wsanchez/deployment/twistedcaldav/config.py	2011-04-20 02:17:46 UTC (rev 7328)
@@ -1,5 +1,5 @@
 ##
-# Copyright (c) 2005-2009 Apple Inc. All rights reserved.
+# Copyright (c) 2005-2011 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.
@@ -32,7 +32,6 @@
 from twistedcaldav.py.plistlib import readPlist
 from twistedcaldav.log import Logger
 from twistedcaldav.log import clearLogLevels, setLogLevelForNamespace, InvalidLogLevelError
-from twistedcaldav.partitions import partitions
 
 log = Logger()
 
@@ -336,14 +335,14 @@
     },
 
     #
-    # Partitioning
+    # Support multiple hosts within a domain
     #
-    "Partitioning" : {
-        "Enabled":             False,   # Partitioning enabled or not
-        "ServerPartitionID":   "",      # Unique ID for this server's partition instance.
-        "PartitionConfigFile": "/etc/caldavd/partitions.plist", # File path for partition information
-        "MaxClients":          5,       # Pool size for connections to each partition
+    "Servers" : {
+        "Enabled": False,                          # Multiple servers/partitions enabled or not
+        "ConfigFile": "/etc/caldavd/servers.xml",  # File path for server information
+        "MaxClients": 5,                           # Pool size for connections to each partition
     },
+    "ServerPartitionID": "",                       # Unique ID for this server's partition instance.
 
     #
     # Performance tuning
@@ -466,7 +465,7 @@
             self.updateDropBox,
             self.updateLogLevels,
             self.updateNotifications,
-            self.updatePartitions,
+            self.updateServers,
         ]
 
     def __str__(self):
@@ -647,19 +646,18 @@
             raise ConfigurationError("Invalid log level: %s" % (e.level))
 
     @staticmethod
-    def updatePartitions(self, items):
-        #
-        # Partitions
-        #
-    
-        if "Partitioning" in items:
-            if items["Partitioning"]["Enabled"]:
-                partitions.setSelfPartition(items["Partitioning"]["ServerPartitionID"])
-                partitions.setMaxClients(items["Partitioning"]["MaxClients"])
-                partitions.readConfig(items["Partitioning"]["PartitionConfigFile"])
-                partitions.installReverseProxies()
+    def updateServers(self, items):
+        import servers
+        if "Servers" in items:
+            if items["Servers"]["Enabled"]:
+                servers.Servers.load()
+                if "ServerPartitionID" in items:
+                    servers.Servers.getThisServer().installReverseProxies(
+                        items["ServerPartitionID"],
+                        items["Servers"]["MaxClients"],
+                )
             else:
-                partitions.clear()
+                servers.Servers.clear()
 
     def updateDefaults(self, items):
         _mergeData(self._defaults, items)

Modified: CalendarServer/branches/users/wsanchez/deployment/twistedcaldav/directory/augment.py
===================================================================
--- CalendarServer/branches/users/wsanchez/deployment/twistedcaldav/directory/augment.py	2011-04-19 22:23:15 UTC (rev 7327)
+++ CalendarServer/branches/users/wsanchez/deployment/twistedcaldav/directory/augment.py	2011-04-20 02:17:46 UTC (rev 7328)
@@ -1,5 +1,5 @@
 ##
-# Copyright (c) 2009 Apple Inc. All rights reserved.
+# Copyright (c) 2009-2011 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.
@@ -35,13 +35,15 @@
         self,
         uid,
         enabled=False,
-        hostedAt="",
+        serverID="",
+        partitionID="",
         enabledForCalendaring=False,
         autoSchedule=False,
     ):
         self.uid = uid
         self.enabled = enabled
-        self.hostedAt = hostedAt
+        self.serverID = serverID
+        self.partitionID = partitionID
         self.enabledForCalendaring = enabledForCalendaring
         self.autoSchedule = autoSchedule
 
@@ -222,9 +224,6 @@
     def __init__(self, dbID, dbapiName, dbapiArgs, **kwargs):
         
         AugmentDB.__init__(self)
-        self.cachedPartitions = {}
-        self.cachedHostedAt = {}
-        
         AbstractADBAPIDatabase.__init__(self, dbID, dbapiName, dbapiArgs, True, **kwargs)
         
     @inlineCallbacks
@@ -251,16 +250,17 @@
         """
         
         # Query for the record information
-        results = (yield self.query("select UID, ENABLED, PARTITIONID, CALENDARING, AUTOSCHEDULE from AUGMENTS where UID = :1", (uid,)))
+        results = (yield self.query("select UID, ENABLED, SERVERID, PARTITIONID, CALENDARING, AUTOSCHEDULE from AUGMENTS where UID = :1", (uid,)))
         if not results:
             returnValue(None)
         else:
-            uid, enabled, partitionid, enabdledForCalendaring, autoSchedule = results[0]
+            uid, enabled, serverid, partitionid, enabdledForCalendaring, autoSchedule = results[0]
             
             record = AugmentRecord(
                 uid = uid,
                 enabled = enabled == "T",
-                hostedAt = (yield self._getPartition(partitionid)),
+                serverID = serverid,
+                partitionID = partitionid,
                 enabledForCalendaring = enabdledForCalendaring == "T",
                 autoSchedule = autoSchedule == "T",
             )
@@ -270,17 +270,16 @@
     @inlineCallbacks
     def addAugmentRecord(self, record, update=False):
 
-        partitionid = (yield self._getPartitionID(record.hostedAt))
-        
         if update:
             yield self.execute(
                 """update AUGMENTS set
-                   (UID, ENABLED, PARTITIONID, CALENDARING, AUTOSCHEDULE) =
-                   (:1, :2, :3, :4, :5) where UID = :6""",
+                   (UID, ENABLED, SERVERID, PARTITIONID, CALENDARING, AUTOSCHEDULE) =
+                   (:1, :2, :3, :4, :5 :6) where UID = :7""",
                 (
                     record.uid,
                     "T" if record.enabled else "F",
-                    partitionid,
+                    record.serverID,
+                    record.partitionID,
                     "T" if record.enabledForCalendaring else "F",
                     "T" if record.autoSchedule else "F",
                     record.uid,
@@ -289,12 +288,13 @@
         else:
             yield self.execute(
                 """insert into AUGMENTS
-                   (UID, ENABLED, PARTITIONID, CALENDARING, AUTOSCHEDULE)
-                   values (:1, :2, :3, :4, :5)""",
+                   (UID, ENABLED, SERVERID, PARTITIONID, CALENDARING, AUTOSCHEDULE)
+                   values (:1, :2, :3, :4, :5, :6)""",
                 (
                     record.uid,
                     "T" if record.enabled else "F",
-                    partitionid,
+                    record.serverID,
+                    record.partitionID,
                     "T" if record.enabledForCalendaring else "F",
                     "T" if record.autoSchedule else "F",
                 )
@@ -304,35 +304,6 @@
 
         return self.query("delete from AUGMENTS where UID = :1", (uid,))
 
-    @inlineCallbacks
-    def _getPartitionID(self, hostedat, createIfMissing=True):
-        
-        # We will use a cache for these as we do not expect changes whilst running
-        try:
-            returnValue(self.cachedHostedAt[hostedat])
-        except KeyError:
-            pass
-
-        partitionid = (yield self.queryOne("select PARTITIONID from PARTITIONS where HOSTEDAT = :1", (hostedat,)))
-        if partitionid == None:
-            yield self.execute("insert into PARTITIONS (HOSTEDAT) values (:1)", (hostedat,))
-            partitionid = (yield self.queryOne("select PARTITIONID from PARTITIONS where HOSTEDAT = :1", (hostedat,)))
-        self.cachedHostedAt[hostedat] = partitionid
-        returnValue(partitionid)
-
-    @inlineCallbacks
-    def _getPartition(self, partitionid):
-        
-        # We will use a cache for these as we do not expect changes whilst running
-        try:
-            returnValue(self.cachedPartitions[partitionid])
-        except KeyError:
-            pass
-
-        partition = (yield self.queryOne("select HOSTEDAT from PARTITIONS where PARTITIONID = :1", (partitionid,)))
-        self.cachedPartitions[partitionid] = partition
-        returnValue(partition)
-
     def _db_version(self):
         """
         @return: the schema version assigned to this index.
@@ -357,20 +328,15 @@
         yield self._create_table("AUGMENTS", (
             ("UID",          "text unique"),
             ("ENABLED",      "text(1)"),
+            ("SERVERID",     "text"),
             ("PARTITIONID",  "text"),
             ("CALENDARING",  "text(1)"),
             ("AUTOSCHEDULE", "text(1)"),
         ))
 
-        yield self._create_table("PARTITIONS", (
-            ("PARTITIONID",   "serial"),
-            ("HOSTEDAT",      "text"),
-        ))
-
     @inlineCallbacks
     def _db_empty_data_tables(self):
         yield self._db_execute("delete from AUGMENTS")
-        yield self._db_execute("delete from PARTITIONS")
 
 class AugmentSqliteDB(ADBAPISqliteMixin, AugmentADAPI):
     """

Modified: CalendarServer/branches/users/wsanchez/deployment/twistedcaldav/directory/directory.py
===================================================================
--- CalendarServer/branches/users/wsanchez/deployment/twistedcaldav/directory/directory.py	2011-04-19 22:23:15 UTC (rev 7327)
+++ CalendarServer/branches/users/wsanchez/deployment/twistedcaldav/directory/directory.py	2011-04-20 02:17:46 UTC (rev 7328)
@@ -1,5 +1,5 @@
 ##
-# Copyright (c) 2006-2007 Apple Inc. All rights reserved.
+# Copyright (c) 2006-2011 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.
@@ -38,7 +38,7 @@
 from twistedcaldav.log import LoggingMixIn
 from twistedcaldav.directory.idirectory import IDirectoryService, IDirectoryRecord
 from twistedcaldav.directory.util import uuidFromName
-from twistedcaldav.partitions import partitions
+from twistedcaldav import servers
 
 class DirectoryService(LoggingMixIn):
     implements(IDirectoryService, ICredentialsChecker)
@@ -153,7 +153,7 @@
     implements(IDirectoryRecord)
 
     def __repr__(self):
-        return "<%s[%s@%s(%s)] %s(%s) %r @ %s>" % (
+        return "<%s[%s@%s(%s)] %s(%s) %r @ %s/#%s>" % (
             self.__class__.__name__,
             self.recordType,
             self.service.guid,
@@ -161,7 +161,8 @@
             self.guid,
             self.shortName,
             self.fullName,
-            self.hostedAt,
+            self.serverURI(),
+            self.partitionID,
         )
 
     def __init__(
@@ -179,7 +180,8 @@
         self.recordType            = recordType
         self.guid                  = guid
         self.enabled               = False
-        self.hostedAt              = ""
+        self.serverID              = ""
+        self.partitionID           = ""
         self.shortName             = shortName
         self.fullName              = fullName
         self.emailAddresses        = emailAddresses
@@ -225,7 +227,8 @@
         
         if augment:
             self.enabled = augment.enabled
-            self.hostedAt = augment.hostedAt
+            self.serverID = augment.serverID
+            self.partitionID = augment.partitionID
             self.enabledForCalendaring = augment.enabledForCalendaring
             self.autoSchedule = augment.autoSchedule
 
@@ -236,7 +239,8 @@
         else:
             # Groups are by default always enabled
             self.enabled = (self.recordType == self.service.recordType_groups)
-            self.hostedAt = ""
+            self.serverID = ""
+            self.partitionID = ""
             self.enabledForCalendaring = False
 
     def members(self):
@@ -263,12 +267,43 @@
     def verifyCredentials(self, credentials):
         return False
 
+    def serverURI(self):
+        """
+        URL of the server hosting this record. Return None if hosted on this server.
+        """
+        if config.Servers.Enabled and self.serverID:
+            return servers.Servers.getServerURIById(self.serverID)
+        else:
+            return None
+    
+    def partitionURI(self):
+        """
+        URL of the server hosting this record. Return None if hosted on this server.
+        """
+        if config.Servers.Enabled and self.serverID:
+            s = servers.Servers.getServerById(self.serverID)
+            if s:
+                return s.getPartitionURIForId(self.partitionID)
+        return None
+    
     def locallyHosted(self):
-        return not self.hostedAt or not config.Partitioning.Enabled or self.hostedAt == config.Partitioning.ServerPartitionID
-    
-    def hostedURL(self):
-        return partitions.getPartitionURL(self.hostedAt)
+        """
+        Hosted on this server/partition instance.
+        """
+        
+        if config.Servers.Enabled and self.serverID:
+            s = servers.Servers.getServerById(self.serverID)
+            if s:
+                return s.thisServer and (not self.partitionID or self.partitionID == config.ServerPartitionID)
+        return True
 
+    def thisServer(self):
+        if config.Servers.Enabled and self.serverID:
+            s = servers.Servers.getServerById(self.serverID)
+            if s:
+                return s.thisServer
+        return True
+
 class DirectoryError(RuntimeError):
     """
     Generic directory error.

Modified: CalendarServer/branches/users/wsanchez/deployment/twistedcaldav/directory/idirectory.py
===================================================================
--- CalendarServer/branches/users/wsanchez/deployment/twistedcaldav/directory/idirectory.py	2011-04-19 22:23:15 UTC (rev 7327)
+++ CalendarServer/branches/users/wsanchez/deployment/twistedcaldav/directory/idirectory.py	2011-04-20 02:17:46 UTC (rev 7328)
@@ -80,7 +80,8 @@
     recordType            = Attribute("The type of this record.")
     guid                  = Attribute("The GUID of this record.")
     enabled               = Attribute("Determines whether this record should be provisioned as a principal.")
-    hostedAt              = Attribute("Identifies the server that actually hosts data for the record.")
+    serverID              = Attribute("Identifies the server that actually hosts data for the record.")
+    partitionID           = Attribute("Identifies the partition node that actually hosts data for the record.")
     shortName             = Attribute("The name of this record.")
     fullName              = Attribute("The full name of this record.")
     emailAddress          = Attribute("The email address of this record.")

Modified: CalendarServer/branches/users/wsanchez/deployment/twistedcaldav/directory/principal.py
===================================================================
--- CalendarServer/branches/users/wsanchez/deployment/twistedcaldav/directory/principal.py	2011-04-19 22:23:15 UTC (rev 7327)
+++ CalendarServer/branches/users/wsanchez/deployment/twistedcaldav/directory/principal.py	2011-04-20 02:17:46 UTC (rev 7328)
@@ -1,5 +1,5 @@
 ##
-# Copyright (c) 2006-2009 Apple Inc. All rights reserved.
+# Copyright (c) 2006-2011 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.
@@ -442,7 +442,8 @@
             """---------------------\n"""
             """Directory GUID: %s\n"""         % (self.record.service.guid,),
             """Realm: %s\n"""                  % (self.record.service.realmName,),
-            """Hosted-At: %s\n"""              % (self.record.hostedAt,) if config.Partitioning.Enabled else "", 
+            """Hosted-At: %s\n"""              % (self.record.serverURI(),) if config.Servers.Enabled else "", 
+            """Partition: %s\n"""              % (self.record.partitionID,) if config.Servers.Enabled and self.record.partitionID else "", 
             """\n"""
             """Principal Information\n"""
             """---------------------\n"""
@@ -603,11 +604,17 @@
     def principalUID(self):
         return self.record.guid
 
+    def serverURI(self):
+        return self.record.serverURI()
+
+    def partitionURI(self):
+        return self.record.partitionURI()
+
     def locallyHosted(self):
         return self.record.locallyHosted()
     
-    def hostedURL(self):
-        return self.record.hostedURL()
+    def thisServer(self):
+        return self.record.thisServer()
 
     ##
     # Extra resource info
@@ -678,7 +685,8 @@
             """---------------------\n"""
             """Directory GUID: %s\n"""         % (self.record.service.guid,),
             """Realm: %s\n"""                  % (self.record.service.realmName,),
-            """Hosted-At: %s\n"""              % (self.record.hostedAt,) if config.Partitioning.Enabled else "", 
+            """Hosted-At: %s\n"""              % (self.record.serverURI(),) if config.Servers.Enabled else "", 
+            """Partition: %s\n"""              % (self.record.partitionID,) if config.Servers.Enabled and self.record.partitionID else "", 
             """\n"""
             """Principal Information\n"""
             """---------------------\n"""
@@ -761,12 +769,17 @@
 
     def _homeChildURL(self, name):
         if not hasattr(self, "calendarHomeURL"):
-            home = self._calendarHome()
-            if home is None:
-                self.calendarHomeURL = None
+            if not hasattr(self.record.service, "calendarHomesCollection"):
                 return None
-            else:
-                self.calendarHomeURL = home.url()
+            self.calendarHomeURL = joinURL(
+                self.record.service.calendarHomesCollection.url(),
+                uidsResourceName,
+                self.record.guid
+            ) + "/"
+
+            # Prefix with other server if needed
+            if not self.thisServer():
+                self.calendarHomeURL = joinURL(self.serverURI(), self.calendarHomeURL)
             
         url = self.calendarHomeURL
         if url is None:

Modified: CalendarServer/branches/users/wsanchez/deployment/twistedcaldav/directory/test/augments-test-default.xml
===================================================================
--- CalendarServer/branches/users/wsanchez/deployment/twistedcaldav/directory/test/augments-test-default.xml	2011-04-19 22:23:15 UTC (rev 7327)
+++ CalendarServer/branches/users/wsanchez/deployment/twistedcaldav/directory/test/augments-test-default.xml	2011-04-20 02:17:46 UTC (rev 7328)
@@ -1,7 +1,7 @@
 <?xml version="1.0" encoding="utf-8"?>
 
 <!--
-Copyright (c) 2009 Apple Inc. All rights reserved.
+Copyright (c) 2009-2011 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.
@@ -23,40 +23,40 @@
     <uid>Default</uid>
     <enable>true</enable>
     <enable-calendar>true</enable-calendar>
-    <hosted-at>00001</hosted-at>
+    <partition-id>00001</partition-id>
   </record>
   <record>
     <uid>Location-Default</uid>
     <enable>true</enable>
     <enable-calendar>true</enable-calendar>
-    <hosted-at>00004</hosted-at>
+    <partition-id>00004</partition-id>
     <auto-schedule>true</auto-schedule>
   </record>
   <record>
     <uid>Location-AA*</uid>
     <enable>true</enable>
     <enable-calendar>true</enable-calendar>
-    <hosted-at>00005</hosted-at>
+    <partition-id>00005</partition-id>
     <auto-schedule>true</auto-schedule>
   </record>
   <record>
     <uid>Resource-Default</uid>
     <enable>true</enable>
     <enable-calendar>true</enable-calendar>
-    <hosted-at>00006</hosted-at>
+    <partition-id>00006</partition-id>
     <auto-schedule>true</auto-schedule>
   </record>
   <record>
     <uid>Resource-AA*</uid>
     <enable>true</enable>
     <enable-calendar>true</enable-calendar>
-    <hosted-at>00007</hosted-at>
+    <partition-id>00007</partition-id>
     <auto-schedule>true</auto-schedule>
   </record>
   <record>
     <uid>AA*</uid>
     <enable>true</enable>
-    <hosted-at>00001</hosted-at>
+    <partition-id>00001</partition-id>
   </record>
   <record>
     <uid>AB*</uid>
@@ -65,13 +65,13 @@
   <record>
     <uid>B*</uid>
     <enable>true</enable>
-    <hosted-at>00002</hosted-at>
+    <partition-id>00002</partition-id>
     <enable-calendar>true</enable-calendar>
   </record>
   <record>
     <uid>C*</uid>
     <enable>true</enable>
-    <hosted-at>00003</hosted-at>
+    <partition-id>00003</partition-id>
     <enable-calendar>true</enable-calendar>
     <auto-schedule>true</auto-schedule>
   </record>
@@ -96,17 +96,17 @@
   <record>
     <uid>5FF60DAD-0BDE-4508-8C77-15F0CA5C8DD1</uid>
     <enable>true</enable>
-    <hosted-at>00001</hosted-at>
+    <partition-id>00001</partition-id>
   </record>
   <record>
     <uid>543D28BA-F74F-4D5F-9243-B3E3A61171E5</uid>
     <enable>true</enable>
-    <hosted-at>00002</hosted-at>
+    <partition-id>00002</partition-id>
   </record>
   <record>
     <uid>6A73326A-F781-47E7-A9F8-AF47364D4152</uid>
     <enable>true</enable>
-    <hosted-at>00002</hosted-at>
+    <partition-id>00002</partition-id>
     <enable-calendar>true</enable-calendar>
     <auto-schedule>true</auto-schedule>
   </record>

Modified: CalendarServer/branches/users/wsanchez/deployment/twistedcaldav/directory/test/augments-test.xml
===================================================================
--- CalendarServer/branches/users/wsanchez/deployment/twistedcaldav/directory/test/augments-test.xml	2011-04-19 22:23:15 UTC (rev 7327)
+++ CalendarServer/branches/users/wsanchez/deployment/twistedcaldav/directory/test/augments-test.xml	2011-04-20 02:17:46 UTC (rev 7328)
@@ -1,7 +1,7 @@
 <?xml version="1.0" encoding="utf-8"?>
 
 <!--
-Copyright (c) 2009 Apple Inc. All rights reserved.
+Copyright (c) 2009-2011 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.
@@ -40,17 +40,17 @@
   <record>
     <uid>5FF60DAD-0BDE-4508-8C77-15F0CA5C8DD1</uid>
     <enable>true</enable>
-    <hosted-at>00001</hosted-at>
+    <partition-id>00001</partition-id>
   </record>
   <record>
     <uid>543D28BA-F74F-4D5F-9243-B3E3A61171E5</uid>
     <enable>true</enable>
-    <hosted-at>00002</hosted-at>
+    <partition-id>00002</partition-id>
   </record>
   <record>
     <uid>6A73326A-F781-47E7-A9F8-AF47364D4152</uid>
     <enable>true</enable>
-    <hosted-at>00002</hosted-at>
+    <partition-id>00002</partition-id>
     <enable>true</enable>
     <enable-calendar>true</enable-calendar>
     <auto-schedule>true</auto-schedule>

Modified: CalendarServer/branches/users/wsanchez/deployment/twistedcaldav/directory/test/test_augment.py
===================================================================
--- CalendarServer/branches/users/wsanchez/deployment/twistedcaldav/directory/test/test_augment.py	2011-04-19 22:23:15 UTC (rev 7327)
+++ CalendarServer/branches/users/wsanchez/deployment/twistedcaldav/directory/test/test_augment.py	2011-04-20 02:17:46 UTC (rev 7328)
@@ -1,5 +1,5 @@
 ##
-# Copyright (c) 2009 Apple Inc. All rights reserved.
+# Copyright (c) 2009-2011 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.
@@ -27,32 +27,32 @@
 xmlFileDefault = os.path.join(os.path.dirname(__file__), "augments-test-default.xml")
 
 testRecords = (
-    {"uid":"D11F03A0-97EA-48AF-9A6C-FAC7F3975766", "enabled":True,  "hostedAt":"",      "enabledForCalendaring":False, "autoSchedule":False},
-    {"uid":"6423F94A-6B76-4A3A-815B-D52CFD77935D", "enabled":True,  "hostedAt":"",      "enabledForCalendaring":True,  "autoSchedule":False},
-    {"uid":"5A985493-EE2C-4665-94CF-4DFEA3A89500", "enabled":False, "hostedAt":"",      "enabledForCalendaring":False, "autoSchedule":False},
-    {"uid":"8B4288F6-CC82-491D-8EF9-642EF4F3E7D0", "enabled":True,  "hostedAt":"",      "enabledForCalendaring":False, "autoSchedule":False},
-    {"uid":"5FF60DAD-0BDE-4508-8C77-15F0CA5C8DD1", "enabled":True,  "hostedAt":"00001", "enabledForCalendaring":False, "autoSchedule":False},
-    {"uid":"543D28BA-F74F-4D5F-9243-B3E3A61171E5", "enabled":True,  "hostedAt":"00002", "enabledForCalendaring":False, "autoSchedule":False},
-    {"uid":"6A73326A-F781-47E7-A9F8-AF47364D4152", "enabled":True,  "hostedAt":"00002", "enabledForCalendaring":True,  "autoSchedule":True },
+    {"uid":"D11F03A0-97EA-48AF-9A6C-FAC7F3975766", "enabled":True,  "partitionID":"",      "enabledForCalendaring":False, "autoSchedule":False},
+    {"uid":"6423F94A-6B76-4A3A-815B-D52CFD77935D", "enabled":True,  "partitionID":"",      "enabledForCalendaring":True,  "autoSchedule":False},
+    {"uid":"5A985493-EE2C-4665-94CF-4DFEA3A89500", "enabled":False, "partitionID":"",      "enabledForCalendaring":False, "autoSchedule":False},
+    {"uid":"8B4288F6-CC82-491D-8EF9-642EF4F3E7D0", "enabled":True,  "partitionID":"",      "enabledForCalendaring":False, "autoSchedule":False},
+    {"uid":"5FF60DAD-0BDE-4508-8C77-15F0CA5C8DD1", "enabled":True,  "partitionID":"00001", "enabledForCalendaring":False, "autoSchedule":False},
+    {"uid":"543D28BA-F74F-4D5F-9243-B3E3A61171E5", "enabled":True,  "partitionID":"00002", "enabledForCalendaring":False, "autoSchedule":False},
+    {"uid":"6A73326A-F781-47E7-A9F8-AF47364D4152", "enabled":True,  "partitionID":"00002", "enabledForCalendaring":True,  "autoSchedule":True },
 )
 
 testRecordWildcardDefault = (
-    {"uid":"A4318887-F2C7-4A70-9056-B88CC8DB26F1", "enabled":True,  "hostedAt":"00001", "enabledForCalendaring":True,  "autoSchedule":False},
-    {"uid":"AA5F935F-3358-4510-A649-B391D63279F2", "enabled":True,  "hostedAt":"00001", "enabledForCalendaring":False, "autoSchedule":False},
-    {"uid":"ABF1A83B-1A29-4E04-BDC3-A6A66ECF27CA", "enabled":False, "hostedAt":"",      "enabledForCalendaring":False, "autoSchedule":False},
-    {"uid":"BC22A734-5E41-4FB7-B5C1-51DC0656DC2F", "enabled":True,  "hostedAt":"00002", "enabledForCalendaring":True,  "autoSchedule":False},
-    {"uid":"C6DEEBB1-E14A-47F2-98BA-7E3BB4353E3A", "enabled":True,  "hostedAt":"00003", "enabledForCalendaring":True,  "autoSchedule":True },
-    {"uid":"AA859321-2C72-4974-ADCF-0CBA0C76F95D", "enabled":True,  "hostedAt":"00001", "enabledForCalendaring":False, "autoSchedule":False},
-    {"uid":"AB7C488B-9ED2-4265-881C-7E2E38A63584", "enabled":False, "hostedAt":"",      "enabledForCalendaring":False, "autoSchedule":False},
-    {"uid":"BB0C0DA1-0545-45F6-8D08-917C554D93A4", "enabled":True,  "hostedAt":"00002", "enabledForCalendaring":True,  "autoSchedule":False},
-    {"uid":"CCD30AD3-582F-4682-8B65-2EDE92C5656E", "enabled":True,  "hostedAt":"00003", "enabledForCalendaring":True,  "autoSchedule":True },
+    {"uid":"A4318887-F2C7-4A70-9056-B88CC8DB26F1", "enabled":True,  "partitionID":"00001", "enabledForCalendaring":True,  "autoSchedule":False},
+    {"uid":"AA5F935F-3358-4510-A649-B391D63279F2", "enabled":True,  "partitionID":"00001", "enabledForCalendaring":False, "autoSchedule":False},
+    {"uid":"ABF1A83B-1A29-4E04-BDC3-A6A66ECF27CA", "enabled":False, "partitionID":"",      "enabledForCalendaring":False, "autoSchedule":False},
+    {"uid":"BC22A734-5E41-4FB7-B5C1-51DC0656DC2F", "enabled":True,  "partitionID":"00002", "enabledForCalendaring":True,  "autoSchedule":False},
+    {"uid":"C6DEEBB1-E14A-47F2-98BA-7E3BB4353E3A", "enabled":True,  "partitionID":"00003", "enabledForCalendaring":True,  "autoSchedule":True },
+    {"uid":"AA859321-2C72-4974-ADCF-0CBA0C76F95D", "enabled":True,  "partitionID":"00001", "enabledForCalendaring":False, "autoSchedule":False},
+    {"uid":"AB7C488B-9ED2-4265-881C-7E2E38A63584", "enabled":False, "partitionID":"",      "enabledForCalendaring":False, "autoSchedule":False},
+    {"uid":"BB0C0DA1-0545-45F6-8D08-917C554D93A4", "enabled":True,  "partitionID":"00002", "enabledForCalendaring":True,  "autoSchedule":False},
+    {"uid":"CCD30AD3-582F-4682-8B65-2EDE92C5656E", "enabled":True,  "partitionID":"00003", "enabledForCalendaring":True,  "autoSchedule":True },
 )
 
 testRecordTypeDefault = (
-    ("locations", {"uid":"A4318887-F2C7-4A70-9056-B88CC8DB26F1", "enabled":True,  "hostedAt":"00004", "enabledForCalendaring":True, "autoSchedule":True}),
-    ("locations", {"uid":"AA5F935F-3358-4510-A649-B391D63279F2", "enabled":True,  "hostedAt":"00005", "enabledForCalendaring":True, "autoSchedule":True}),
-    ("resources", {"uid":"A5318887-F2C7-4A70-9056-B88CC8DB26F1", "enabled":True,  "hostedAt":"00006", "enabledForCalendaring":True, "autoSchedule":True}),
-    ("resources", {"uid":"AA6F935F-3358-4510-A649-B391D63279F2", "enabled":True,  "hostedAt":"00007", "enabledForCalendaring":True, "autoSchedule":True}),
+    ("locations", {"uid":"A4318887-F2C7-4A70-9056-B88CC8DB26F1", "enabled":True,  "partitionID":"00004", "enabledForCalendaring":True, "autoSchedule":True}),
+    ("locations", {"uid":"AA5F935F-3358-4510-A649-B391D63279F2", "enabled":True,  "partitionID":"00005", "enabledForCalendaring":True, "autoSchedule":True}),
+    ("resources", {"uid":"A5318887-F2C7-4A70-9056-B88CC8DB26F1", "enabled":True,  "partitionID":"00006", "enabledForCalendaring":True, "autoSchedule":True}),
+    ("resources", {"uid":"AA6F935F-3358-4510-A649-B391D63279F2", "enabled":True,  "partitionID":"00007", "enabledForCalendaring":True, "autoSchedule":True}),
 )
 
 class AugmentTests(TestCase):

Modified: CalendarServer/branches/users/wsanchez/deployment/twistedcaldav/directory/xmlaugmentsparser.py
===================================================================
--- CalendarServer/branches/users/wsanchez/deployment/twistedcaldav/directory/xmlaugmentsparser.py	2011-04-19 22:23:15 UTC (rev 7327)
+++ CalendarServer/branches/users/wsanchez/deployment/twistedcaldav/directory/xmlaugmentsparser.py	2011-04-20 02:17:46 UTC (rev 7328)
@@ -1,5 +1,5 @@
 ##
-# Copyright (c) 2009 Apple Inc. All rights reserved.
+# Copyright (c) 2009-2011 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.
@@ -35,7 +35,8 @@
 
 ELEMENT_UID               = "uid"
 ELEMENT_ENABLE            = "enable"
-ELEMENT_HOSTEDAT          = "hosted-at"
+ELEMENT_SERVERID          = "server-id"
+ELEMENT_PARTITIONID       = "partition-id"
 ELEMENT_ENABLECALENDAR    = "enable-calendar"
 ELEMENT_AUTOSCHEDULE      = "auto-schedule"
 
@@ -47,7 +48,8 @@
 ELEMENT_AUGMENTRECORD_MAP = {
     ELEMENT_UID:            "uid",
     ELEMENT_ENABLE:         "enabled",
-    ELEMENT_HOSTEDAT:       "hostedAt",
+    ELEMENT_SERVERID:          "serverID",
+    ELEMENT_PARTITIONID:       "partitionID",
     ELEMENT_ENABLECALENDAR: "enabledForCalendaring",
     ELEMENT_AUTOSCHEDULE:   "autoSchedule",
 }
@@ -94,7 +96,8 @@
                 
                 if node.tag in (
                     ELEMENT_UID,
-                    ELEMENT_HOSTEDAT,
+                    ELEMENT_SERVERID,
+                    ELEMENT_PARTITIONID,
                 ):
                     fields[node.tag] = node.text
                 elif node.tag in (

Modified: CalendarServer/branches/users/wsanchez/deployment/twistedcaldav/method/report_common.py
===================================================================
--- CalendarServer/branches/users/wsanchez/deployment/twistedcaldav/method/report_common.py	2011-04-19 22:23:15 UTC (rev 7327)
+++ CalendarServer/branches/users/wsanchez/deployment/twistedcaldav/method/report_common.py	2011-04-20 02:17:46 UTC (rev 7328)
@@ -1,5 +1,5 @@
 ##
-# Copyright (c) 2006-2007 Apple Inc. All rights reserved.
+# Copyright (c) 2006-2011 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.
@@ -287,7 +287,9 @@
 fbtype_index_mapper = {'B': 0, 'T': 1, 'U': 2}
 
 def generateFreeBusyInfo(request, calresource, fbinfo, timerange, matchtotal,
-                         excludeuid=None, organizer=None, organizerPrincipal=None, same_calendar_user=False):
+                         excludeuid=None, organizer=None,
+                         organizerPrincipal=None, same_calendar_user=False,
+                         servertoserver=False):
     """
     Run a free busy report on the specified calendar collection
     accumulating the free busy info for later processing.
@@ -302,16 +304,21 @@
         This is used in conjunction with the UID value to process exclusions.
     @param same_calendar_user:   a C{bool} indicating whether the calendar user requesting the free-busy information
         is the same as the calendar user being targeted.
+    @param servertoserver: a C{bool} indicating whether we are doing a local or
+        remote lookup request.
     """
     
     # First check the privilege on this collection
-#    try:
-#        d = waitForDeferred(calresource.checkPrivileges(request, (caldavxml.ReadFreeBusy(),), principal=organizerPrincipal))
-#        yield d
-#        d.getResult()
-#    except AccessDeniedError:
-#        yield matchtotal
-#        return
+    # Per-calendar ACLs never used - commented this out to improve performance 
+    # TODO: for server-to-server we bypass this right now as we have no way to authorize external users.
+#    if not servertoserver:
+#        try:
+#            d = waitForDeferred(calresource.checkPrivileges(request, (caldavxml.ReadFreeBusy(),), principal=organizerPrincipal))
+#            yield d
+#            d.getResult()
+#        except AccessDeniedError:
+#            yield matchtotal
+#            return
 
     # May need organizer principal
     organizer_principal = calresource.principalForCalendarUserAddress(organizer) if organizer else None
@@ -369,12 +376,15 @@
         yield child
         child = child.getResult()
 
-#        try:
-#            d = waitForDeferred(child.checkPrivileges(request, (caldavxml.ReadFreeBusy(),), inherited_aces=filteredaces, principal=organizerPrincipal))
-#            yield d
-#            d.getResult()
-#        except AccessDeniedError:
-#            continue
+        # Per-resource ACLs never used - commented this out to improve performance 
+#        # TODO: for server-to-server we bypass this right now as we have no way to authorize external users.
+#        if not servertoserver:
+#            try:
+#                d = waitForDeferred(child.checkPrivileges(request, (caldavxml.ReadFreeBusy(),), inherited_aces=filteredaces, principal=organizerPrincipal))
+#                yield d
+#                d.getResult()
+#            except AccessDeniedError:
+#                continue
 
         # Short-cut - if an fbtype exists we can use that
         if type == "VEVENT" and aggregated_resources[key][0][3] != '?':

Deleted: CalendarServer/branches/users/wsanchez/deployment/twistedcaldav/partitions.py
===================================================================
--- CalendarServer/branches/users/wsanchez/deployment/twistedcaldav/partitions.py	2011-04-19 22:23:15 UTC (rev 7327)
+++ CalendarServer/branches/users/wsanchez/deployment/twistedcaldav/partitions.py	2011-04-20 02:17:46 UTC (rev 7328)
@@ -1,71 +0,0 @@
-##
-# Copyright (c) 2009 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 twistedcaldav.client.pool import installPool
-from twistedcaldav.log import Logger
-from twistedcaldav.py.plistlib import readPlist
-
-"""
-Collection of classes for managing partition information for a group of servers.
-"""
-
-log = Logger()
-
-class Partitions(object):
-
-    def __init__(self):
-        
-        self.clear()
-
-    def clear(self):
-        self.partitions = {}
-        self.ownUID = ""
-        self.maxClients = 5
-
-    def readConfig(self, plistpath):
-        try:
-            dataDict = readPlist(plistpath)
-        except (IOError, OSError):                                    
-            log.error("Configuration file does not exist or is inaccessible: %s" % (self._configFileName,))
-            return
-        
-        for partition in dataDict.get("partitions", ()):
-            uid = partition.get("uid", None)
-            url = partition.get("url", None)
-            if uid and url:
-                self.partitions[uid] = url
-
-    def setSelfPartition(self, uid):
-        self.ownUID = uid
-
-    def setMaxClients(self, maxClients):
-        self.maxClients = maxClients
-
-    def getPartitionURL(self, uid):
-        # When the UID matches this server return an empty string
-        return self.partitions.get(uid, None) if uid != self.ownUID else ""
-
-    def installReverseProxies(self):
-        
-        for partition, url in self.partitions.iteritems():
-            if partition != self.ownUID:
-                installPool(
-                    partition,
-                    url,
-                    self.maxClients,
-                )
-
-partitions = Partitions()

Modified: CalendarServer/branches/users/wsanchez/deployment/twistedcaldav/query/expression.py
===================================================================
--- CalendarServer/branches/users/wsanchez/deployment/twistedcaldav/query/expression.py	2011-04-19 22:23:15 UTC (rev 7327)
+++ CalendarServer/branches/users/wsanchez/deployment/twistedcaldav/query/expression.py	2011-04-20 02:17:46 UTC (rev 7328)
@@ -1,5 +1,5 @@
 ##
-# Copyright (c) 2006-2007 Apple Inc. All rights reserved.
+# Copyright (c) 2006-2011 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.
@@ -102,7 +102,7 @@
     """
     
     def __init__(self, expression):
-       super(notExpression, self).__init__([expression])
+        super(notExpression, self).__init__([expression])
 
     def operator(self):
         return "NOT"

Modified: CalendarServer/branches/users/wsanchez/deployment/twistedcaldav/scheduling/addressmapping.py
===================================================================
--- CalendarServer/branches/users/wsanchez/deployment/twistedcaldav/scheduling/addressmapping.py	2011-04-19 22:23:15 UTC (rev 7327)
+++ CalendarServer/branches/users/wsanchez/deployment/twistedcaldav/scheduling/addressmapping.py	2011-04-20 02:17:46 UTC (rev 7328)
@@ -1,5 +1,5 @@
 ##
-# Copyright (c) 2005-2009 Apple Inc. All rights reserved.
+# Copyright (c) 2005-2011 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.
@@ -19,8 +19,8 @@
 from twistedcaldav.log import Logger
 from twistedcaldav.memcacher import Memcacher
 from twistedcaldav.scheduling.caldav import ScheduleViaCalDAV
-from twistedcaldav.scheduling.cuaddress import LocalCalendarUser,\
-    RemoteCalendarUser, InvalidCalendarUser, PartitionedCalendarUser
+from twistedcaldav.scheduling.cuaddress import RemoteCalendarUser, InvalidCalendarUser,\
+    calendarUserFromPrincipal
 from twistedcaldav.scheduling.delivery import DeliveryService
 from twistedcaldav.scheduling.ischedule import ScheduleViaISchedule
 
@@ -50,7 +50,7 @@
         
         # If we have a principal always treat the user as local or partitioned
         if principal:
-            returnValue(LocalCalendarUser(cuaddr, principal) if principal.locallyHosted() else PartitionedCalendarUser(cuaddr, principal))
+            returnValue(calendarUserFromPrincipal(cuaddr, principal))
 
         # Get the type
         cuaddr_type = (yield self.getCalendarUserServiceType(cuaddr))

Modified: CalendarServer/branches/users/wsanchez/deployment/twistedcaldav/scheduling/caldav.py
===================================================================
--- CalendarServer/branches/users/wsanchez/deployment/twistedcaldav/scheduling/caldav.py	2011-04-19 22:23:15 UTC (rev 7327)
+++ CalendarServer/branches/users/wsanchez/deployment/twistedcaldav/scheduling/caldav.py	2011-04-20 02:17:46 UTC (rev 7328)
@@ -1,5 +1,5 @@
 ##
-# Copyright (c) 2005-2007 Apple Inc. All rights reserved.
+# Copyright (c) 2005-2011 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.
@@ -39,7 +39,7 @@
 from twistedcaldav.method import report_common
 from twistedcaldav.resource import isCalendarCollectionResource
 from twistedcaldav.scheduling.cuaddress import LocalCalendarUser, RemoteCalendarUser,\
-    PartitionedCalendarUser
+    PartitionedCalendarUser, OtherServerCalendarUser
 from twistedcaldav.scheduling.delivery import DeliveryService
 from twistedcaldav.scheduling.itip import iTIPRequestStatus
 from twistedcaldav.scheduling.itip import handleRequest
@@ -98,7 +98,7 @@
         uid = self.scheduler.calendar.resourceUID()
 
         organizerPrincipal = None
-        if type(self.scheduler.organizer) in (LocalCalendarUser, PartitionedCalendarUser,):
+        if type(self.scheduler.organizer) in (LocalCalendarUser, PartitionedCalendarUser, OtherServerCalendarUser,):
             organizerPrincipal = davxml.Principal(davxml.HRef(self.scheduler.organizer.principal.principalURL()))
 
         for recipient in self.recipients:
@@ -252,6 +252,7 @@
                 organizer = self.scheduler.organizer.cuaddr,
                 organizerPrincipal = organizerPrincipal,
                 same_calendar_user = same_calendar_user,
+                servertoserver = remote,
             ))
     
         # Build VFREEBUSY iTIP reply for this recipient

Modified: CalendarServer/branches/users/wsanchez/deployment/twistedcaldav/scheduling/cuaddress.py
===================================================================
--- CalendarServer/branches/users/wsanchez/deployment/twistedcaldav/scheduling/cuaddress.py	2011-04-19 22:23:15 UTC (rev 7327)
+++ CalendarServer/branches/users/wsanchez/deployment/twistedcaldav/scheduling/cuaddress.py	2011-04-20 02:17:46 UTC (rev 7328)
@@ -1,5 +1,5 @@
 ##
-# Copyright (c) 2005-2007 Apple Inc. All rights reserved.
+# Copyright (c) 2005-2011 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.
@@ -19,6 +19,8 @@
 
 __all__ = [
     "LocalCalendarUser",
+    "PartitionedCalendarUser",
+    "OtherServerCalendarUser",
     "RemoteCalendarUser",
     "InvalidCalendarUser",
     "normalizeCUAddr",
@@ -51,6 +53,15 @@
     def __str__(self):
         return "Partitioned calendar user: %s" % (self.cuaddr,)
 
+class OtherServerCalendarUser(CalendarUser):
+    def __init__(self, cuaddr, principal):
+        self.cuaddr = cuaddr
+        self.principal = principal
+        self.serviceType = DeliveryService.serviceType_ischedule
+
+    def __str__(self):
+        return "Other server calendar user: %s" % (self.cuaddr,)
+
 class RemoteCalendarUser(CalendarUser):
     def __init__(self, cuaddr):
         self.cuaddr = cuaddr
@@ -92,3 +103,15 @@
         return addr.rstrip("/")
     else:
         return addr
+
+def calendarUserFromPrincipal(recipient, principal, inbox=None, inboxURL=None):
+    """
+    Get the appropriate calendar user address class for the provided principal.
+    """
+    
+    if principal.locallyHosted():
+        return LocalCalendarUser(recipient, principal, inbox, inboxURL)
+    elif principal.thisServer():
+        return PartitionedCalendarUser(recipient, principal)
+    else:
+        return OtherServerCalendarUser(recipient, principal)

Modified: CalendarServer/branches/users/wsanchez/deployment/twistedcaldav/scheduling/ischedule.py
===================================================================
--- CalendarServer/branches/users/wsanchez/deployment/twistedcaldav/scheduling/ischedule.py	2011-04-19 22:23:15 UTC (rev 7327)
+++ CalendarServer/branches/users/wsanchez/deployment/twistedcaldav/scheduling/ischedule.py	2011-04-20 02:17:46 UTC (rev 7328)
@@ -1,5 +1,5 @@
 ##
-# Copyright (c) 2005-2007 Apple Inc. All rights reserved.
+# Copyright (c) 2005-2011 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.
@@ -39,8 +39,8 @@
     IScheduleServerRecord
 from twistedcaldav.scheduling.itip import iTIPRequestStatus
 from twistedcaldav.util import utf8String
-from twistedcaldav.scheduling.cuaddress import RemoteCalendarUser,\
-    PartitionedCalendarUser
+from twistedcaldav.scheduling.cuaddress import PartitionedCalendarUser, RemoteCalendarUser,\
+    OtherServerCalendarUser
 
 import OpenSSL
 
@@ -83,6 +83,8 @@
                 server = servermgr.mapDomain(recipient.domain)
             elif isinstance(recipient, PartitionedCalendarUser):
                 server = self._getServerForPartitionedUser(recipient)
+            elif isinstance(recipient, OtherServerCalendarUser):
+                server = self._getServerForOtherServerUser(recipient)
             else:
                 assert False, "Incorrect calendar user address class"
             if not server:
@@ -121,13 +123,25 @@
         if not hasattr(self, "partitionedServers"):
             self.partitionedServers = {}
             
-        partition = recipient.principal.hostedURL()
+        partition = recipient.principal.partitionURI()
         if partition not in self.partitionedServers:
             self.partitionedServers[partition] = IScheduleServerRecord(uri=joinURL(partition, "/ischedule"))
             self.partitionedServers[partition].unNormalizeAddresses = False
         
         return self.partitionedServers[partition]
 
+    def _getServerForOtherServerUser(self, recipient):
+        
+        if not hasattr(self, "otherServers"):
+            self.otherServers = {}
+            
+        server = recipient.principal.serverURI()
+        if server not in self.otherServers:
+            self.otherServers[server] = IScheduleServerRecord(uri=joinURL(server, "/ischedule"))
+            self.otherServers[server].unNormalizeAddresses = False
+        
+        return self.otherServers[server]
+
 class IScheduleRequest(object):
     
     def __init__(self, scheduler, server, recipients, responses):

Modified: CalendarServer/branches/users/wsanchez/deployment/twistedcaldav/scheduling/itip.py
===================================================================
--- CalendarServer/branches/users/wsanchez/deployment/twistedcaldav/scheduling/itip.py	2011-04-19 22:23:15 UTC (rev 7327)
+++ CalendarServer/branches/users/wsanchez/deployment/twistedcaldav/scheduling/itip.py	2011-04-20 02:17:46 UTC (rev 7328)
@@ -1,5 +1,5 @@
 ##
-# Copyright (c) 2006-2007 Apple Inc. All rights reserved.
+# Copyright (c) 2006-2011 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.
@@ -473,7 +473,7 @@
                 tr = caldavxml.TimeRange(start="20000101", end="20000101")
                 tr.start = instance.start
                 tr.end = instance.end
-                yield report_common.generateFreeBusyInfo(request, testcal, fbinfo, tr, 0, uid)
+                yield report_common.generateFreeBusyInfo(request, testcal, fbinfo, tr, 0, uid, servertoserver=True)
                 
                 # If any fbinfo entries exist we have an overlap
                 if len(fbinfo[0]) or len(fbinfo[1]) or len(fbinfo[2]):

Modified: CalendarServer/branches/users/wsanchez/deployment/twistedcaldav/scheduling/scheduler.py
===================================================================
--- CalendarServer/branches/users/wsanchez/deployment/twistedcaldav/scheduling/scheduler.py	2011-04-19 22:23:15 UTC (rev 7327)
+++ CalendarServer/branches/users/wsanchez/deployment/twistedcaldav/scheduling/scheduler.py	2011-04-20 02:17:46 UTC (rev 7328)
@@ -1,5 +1,5 @@
 ##
-# Copyright (c) 2005-2008 Apple Inc. All rights reserved.
+# Copyright (c) 2005-2011 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.
@@ -15,6 +15,7 @@
 ##
 
 
+from twisted.internet.abstract import isIPAddress
 from twisted.internet.defer import inlineCallbacks, returnValue
 
 from twisted.python.failure import Failure
@@ -37,7 +38,10 @@
 from twistedcaldav.scheduling import addressmapping
 from twistedcaldav.scheduling.caldav import ScheduleViaCalDAV
 from twistedcaldav.scheduling.cuaddress import InvalidCalendarUser,\
-    LocalCalendarUser, RemoteCalendarUser, PartitionedCalendarUser
+    calendarUserFromPrincipal, OtherServerCalendarUser
+from twistedcaldav.scheduling.cuaddress import LocalCalendarUser
+from twistedcaldav.scheduling.cuaddress import RemoteCalendarUser
+from twistedcaldav.scheduling.cuaddress import PartitionedCalendarUser
 from twistedcaldav.scheduling.ischedule import ScheduleViaISchedule
 from twistedcaldav.scheduling.ischeduleservers import IScheduleServers
 from twistedcaldav.scheduling.itip import iTIPRequestStatus
@@ -385,6 +389,8 @@
     
         # Loop over each recipient and aggregate into lists by service types.
         caldav_recipients = []
+        partitioned_recipients = []
+        otherserver_recipients = []
         remote_recipients = []
         for ctr, recipient in enumerate(self.recipients):
     
@@ -400,8 +406,11 @@
                 caldav_recipients.append(recipient)
 
             elif isinstance(recipient, PartitionedCalendarUser):
-                remote_recipients.append(recipient)
+                partitioned_recipients.append(recipient)
 
+            elif isinstance(recipient, OtherServerCalendarUser):
+                otherserver_recipients.append(recipient)
+
             elif isinstance(recipient, RemoteCalendarUser):
                 remote_recipients.append(recipient)
 
@@ -413,6 +422,14 @@
         if caldav_recipients:
             yield self.generateLocalSchedulingResponses(caldav_recipients, responses, freebusy)
 
+        # Now process partitioned recipients
+        if partitioned_recipients:
+            yield self.generateRemoteSchedulingResponses(partitioned_recipients, responses, freebusy)
+
+        # Now process other recipients
+        if otherserver_recipients:
+            yield self.generateRemoteSchedulingResponses(otherserver_recipients, responses, freebusy)
+
         # To reduce chatter, we suppress certain messages
         if not getattr(self.request, 'suppressRefresh', False):
 
@@ -547,7 +564,7 @@
                     inbox = (yield self.request.locateResource(inboxURL)) if principal.locallyHosted() else "dummy"
 
                 if inbox:
-                    results.append(LocalCalendarUser(recipient, principal, inbox, inboxURL) if principal.locallyHosted() else PartitionedCalendarUser(recipient, principal))
+                    results.append(calendarUserFromPrincipal(recipient, principal, inbox, inboxURL))
                 else:
                     log.err("No schedule inbox for principal: %s" % (principal,))
                     results.append(InvalidCalendarUser(recipient))
@@ -670,7 +687,7 @@
                     inbox = (yield self.request.locateResource(inboxURL)) if principal.locallyHosted() else "dummy"
 
                 if inbox:
-                    results.append(LocalCalendarUser(recipient, principal, inbox, inboxURL) if principal.locallyHosted() else PartitionedCalendarUser(recipient, principal))
+                    results.append(calendarUserFromPrincipal(recipient, principal, inbox, inboxURL))
                 else:
                     log.err("No schedule inbox for principal: %s" % (principal,))
                     results.append(InvalidCalendarUser(recipient))
@@ -699,8 +716,8 @@
                 log.err("Cannot use originator that is on this server: %s" % (self.originator,))
                 raise HTTPError(ErrorResponse(responsecode.FORBIDDEN, (caldav_namespace, "originator-allowed")))
             else:
-                self.originator = PartitionedCalendarUser(self.originator, originatorPrincipal)
-                #self._validPartitionServer()
+                self.originator = calendarUserFromPrincipal(self.originator, originatorPrincipal)
+                self._validAlternateServer(originatorPrincipal)
         else:
             self.originator = RemoteCalendarUser(self.originator)
             self._validiScheduleServer()
@@ -753,13 +770,16 @@
                 log.err("Originator not on allowed server: %s" % (self.originator,))
                 raise HTTPError(ErrorResponse(responsecode.FORBIDDEN, (caldav_namespace, "originator-allowed")))
 
-    def _validPartitionServer(self, principal):
+    def _validAlternateServer(self, principal):
         """
         Check the validity of the partitioned host.
         """
 
-        # Extract expected host/port
-        expected_uri = principal.hostedURL()
+        # Extract expected host/port. This will be the partitionURI, or if no partitions,
+        # the serverURI
+        expected_uri = principal.partitionURI()
+        if expected_uri is None:
+            expected_uri = principal.serverURI()
         expected_uri = urlparse.urlparse(expected_uri)
     
         # Get the request IP and map to hostname.
@@ -767,15 +787,16 @@
         
         # First compare as dotted IP
         matched = False
-        if clientip == expected_uri.hostname:
-            matched = True
+        if isIPAddress(expected_uri.hostname):
+            if clientip == expected_uri.hostname:
+                matched = True
         else:
-            # Now do hostname lookup
+            # Now do expected hostname -> IP lookup
             try:
-                host, aliases, _ignore_ips = socket.gethostbyaddr(clientip)
-                for hostname in itertools.chain((host,), aliases):
-                    # Try host match
-                    if hostname == expected_uri.hostname:
+                # So now try the lookup of the expected host
+                _ignore_host, _ignore_aliases, ips = socket.gethostbyname_ex(expected_uri.hostname)
+                for ip in ips:
+                    if ip == clientip:
                         matched = True
                         break
             except socket.herror, e:
@@ -801,8 +822,8 @@
                     raise HTTPError(ErrorResponse(responsecode.FORBIDDEN, (caldav_namespace, "organizer-allowed")))
                 else:
                     # Check that the origin server is the correct partition
-                    self.organizer = PartitionedCalendarUser(organizer, organizerPrincipal)
-                    self._validPartitionServer(self.organizer.principal)
+                    self.organizer = calendarUserFromPrincipal(organizer, organizerPrincipal)
+                    self._validAlternateServer(self.organizer.principal)
             else:
                 localUser = (yield addressmapping.mapper.isCalendarUserInMyDomain(organizer))
                 if localUser:
@@ -828,7 +849,7 @@
                 log.err("Invalid ATTENDEE in calendar data: %s" % (self.calendardata,))
                 raise HTTPError(ErrorResponse(responsecode.FORBIDDEN, (caldav_namespace, "attendee-allowed")))
             else:
-                self._validPartitionServer(attendeePrincipal)                
+                self._validAlternateServer(attendeePrincipal)                
         else:
             localUser = (yield addressmapping.mapper.isCalendarUserInMyDomain(self.attendee))
             if localUser:

Added: CalendarServer/branches/users/wsanchez/deployment/twistedcaldav/servers.py
===================================================================
--- CalendarServer/branches/users/wsanchez/deployment/twistedcaldav/servers.py	                        (rev 0)
+++ CalendarServer/branches/users/wsanchez/deployment/twistedcaldav/servers.py	2011-04-20 02:17:46 UTC (rev 7328)
@@ -0,0 +1,194 @@
+##
+# Copyright (c) 2011 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 twistedcaldav.client.pool import installPool
+from twistedcaldav.config import config
+from twistedcaldav.log import Logger
+from twistedcaldav.xmlutil import readXML
+import urlparse
+
+"""
+XML based server configuration file handling.
+
+This is used in an environment where more than one server is being used within a single domain. i.e., all
+the principals across the whole domain need to be able to directly schedule each other and know of each others
+existence. A common scenario would be a production server and a development/test server.
+
+Each server is identified by an id and url. The id is used when assigning principals to a specific server. Each
+server can also support multiple partitions, and each of those is identified by an id and url, with the id also
+being used to assign principals to a specific partition.
+"""
+
+__all__ = [
+    "Servers",
+]
+
+log = Logger()
+
+class ServersDB(object):
+    """
+    Represents the set of servers within the same domain.
+    """
+    
+    def __init__(self):
+        
+        self._servers = {}
+        self._xmlFile = None
+        self._thisServer = None
+
+    def load(self, xmlFile=None):
+        if self._xmlFile is None or xmlFile is not None:
+            self._servers = {}
+            if xmlFile:
+                self._xmlFile = xmlFile
+            else:
+                self._xmlFile = config.Servers.ConfigFile
+        self._servers = ServersParser.parse(self._xmlFile)
+        for server in self._servers.values():
+            if server.thisServer:
+                self._thisServer = server
+                break
+        else:
+            raise ValueError("No server in %s matches this server." % (self._xmlFile,))
+    
+    def clear(self):
+        self._servers = {}
+        self._xmlFile = None
+        self._thisServer = None
+
+    def getServerById(self, id):
+        return self._servers.get(id)
+        
+    def getServerURIById(self, id):
+        try:
+            return self._servers[id].uri
+        except KeyError:
+            return None
+    
+    def getThisServer(self):
+        return self._thisServer
+
+Servers = ServersDB()   # Global server DB
+
+class Server(object):
+    """
+    Represents a server which may itself be partitioned.
+    """
+    
+    def __init__(self):
+        self.id = None
+        self.uri = None
+        self.thisServer = False
+        self.partitions = {}
+    
+    def check(self):
+        # Check whether this matches the current server
+        parsed_uri = urlparse.urlparse(self.uri)
+        if parsed_uri.hostname == config.ServerHostName:
+            if parsed_uri.scheme == "http":
+                if config.HTTPPort:
+                    self.thisServer = parsed_uri.port in (config.HTTPPort,) + tuple(config.BindHTTPPorts)
+            elif parsed_uri.scheme == "https":
+                if config.SSLPort:
+                    self.thisServer = parsed_uri.port in (config.SSLPort,) + tuple(config.BindSSLPorts)
+        
+    def addPartition(self, id, uri):
+        self.partitions[id] = uri
+    
+    def getPartitionURIForId(self, id):
+        return self.partitions.get(id)
+    
+    def installReverseProxies(self, ownUID, maxClients):
+        
+        for partition, url in self.partitions.iteritems():
+            if partition != ownUID:
+                installPool(
+                    partition,
+                    url,
+                    maxClients,
+                )
+    
+        
+        
+ELEMENT_SERVERS                 = "servers"
+ELEMENT_SERVER                  = "server"
+ELEMENT_ID                      = "id"
+ELEMENT_URI                     = "uri"
+ELEMENT_PARTITIONS              = "partitions"
+ELEMENT_PARTITION               = "partition"
+
+class ServersParser(object):
+    """
+    Servers configuration file parser.
+    """
+    @staticmethod
+    def parse(xmlFile):
+
+        results = {}
+
+        # Read in XML
+        try:
+            _ignore_tree, servers_node = readXML(xmlFile, ELEMENT_SERVERS)
+        except ValueError, e:
+            log.error("XML parse error for '%s' because: %s" % (xmlFile, e,), raiseException=RuntimeError)
+
+        for child in servers_node.getchildren():
+            
+            if child.tag != ELEMENT_SERVER:
+                log.error("Unknown server type: '%s' in servers file: '%s'" % (child.tag, xmlFile,), raiseException=RuntimeError)
+
+            server = Server()
+            for node in child.getchildren():
+                if node.tag == ELEMENT_ID:
+                    server.id = node.text
+                elif node.tag == ELEMENT_URI:
+                    server.uri = node.text
+                elif node.tag == ELEMENT_PARTITIONS:
+                    ServersParser._parsePartition(xmlFile, node, server)
+                else:
+                    log.error("Invalid element '%s' in servers file: '%s'" % (node.tag, xmlFile,), raiseException=RuntimeError)
+
+            if server.id is None or server.uri is None:
+                log.error("Invalid partition '%s' in servers file: '%s'" % (child.tag, xmlFile,), raiseException=RuntimeError)
+
+            server.check()
+            results[server.id] = server
+
+        return results
+
+    @staticmethod
+    def _parsePartition(xmlFile, partitions, server):
+
+        for child in partitions.getchildren():
+            
+            if child.tag != ELEMENT_PARTITION:
+                log.error("Unknown partition type: '%s' in servers file: '%s'" % (child.tag, xmlFile,), raiseException=RuntimeError)
+
+            id = None
+            uri = None
+            for node in child.getchildren():
+                if node.tag == ELEMENT_ID:
+                    id = node.text
+                elif node.tag == ELEMENT_URI:
+                    uri = node.text
+                else:
+                    log.error("Invalid element '%s' in augment file: '%s'" % (node.tag, xmlFile,), raiseException=RuntimeError)
+        
+            if id is None or uri is None:
+                log.error("Invalid partition '%s' in servers file: '%s'" % (child.tag, xmlFile,), raiseException=RuntimeError)
+            
+            server.addPartition(id, uri)

Modified: CalendarServer/branches/users/wsanchez/deployment/twistedcaldav/static.py
===================================================================
--- CalendarServer/branches/users/wsanchez/deployment/twistedcaldav/static.py	2011-04-19 22:23:15 UTC (rev 7327)
+++ CalendarServer/branches/users/wsanchez/deployment/twistedcaldav/static.py	2011-04-20 02:17:46 UTC (rev 7328)
@@ -1,5 +1,5 @@
 ##
-# Copyright (c) 2005-2007 Apple Inc. All rights reserved.
+# Copyright (c) 2005-2011 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.
@@ -664,9 +664,11 @@
     
                 assert child.exists()
         
-        else:
+        elif record.thisServer():
             childPath = self.fp.child(name[0:2]).child(name[2:4]).child(name)
             child = CalendarHomeReverseProxyFile(childPath.path, self, record)
+        else:
+            child = None # Use a redirect?
 
         return child
 
@@ -680,7 +682,7 @@
         self.parent = parent
         self.record = record
         
-        super(CalendarHomeReverseProxyFile, self).__init__(self.record.hostedAt)
+        super(CalendarHomeReverseProxyFile, self).__init__(self.record.partitionID)
     
     def url(self):
         return joinURL(self.parent.url(), self.record.guid)

Modified: CalendarServer/branches/users/wsanchez/deployment/twistedcaldav/test/test_tap.py
===================================================================
--- CalendarServer/branches/users/wsanchez/deployment/twistedcaldav/test/test_tap.py	2011-04-19 22:23:15 UTC (rev 7327)
+++ CalendarServer/branches/users/wsanchez/deployment/twistedcaldav/test/test_tap.py	2011-04-20 02:17:46 UTC (rev 7328)
@@ -1,5 +1,5 @@
 ##
-# Copyright (c) 2007 Apple Inc. All rights reserved.
+# Copyright (c) 2007-2011 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.
@@ -231,7 +231,11 @@
         a '/' seperated path.  Such as '-o MultiProcess/ProcessCount=1'
         """
 
-        argv = ['-o', 'MultiProcess/ProcessCount=102']
+        myConfig = deepcopy(config_mod.defaultConfig)
+        myConfigFile = self.mktemp()
+        writePlist(myConfig, myConfigFile)
+
+        argv = ['-f', myConfigFile, '-o', 'MultiProcess/ProcessCount=102']
         self.config.parseOptions(argv)
 
         self.assertEquals(config.MultiProcess['ProcessCount'], 102)
@@ -345,6 +349,7 @@
         self.writeConfig()
         self.assertRaises(UsageError, self.makeService)
 
+    test_makeServiceDispatcher.skip = True
 
 class SlaveServiceTest(BaseServiceMakerTests):
     """

Added: CalendarServer/branches/users/wsanchez/deployment/twistedcaldav/xmlutil.py
===================================================================
--- CalendarServer/branches/users/wsanchez/deployment/twistedcaldav/xmlutil.py	                        (rev 0)
+++ CalendarServer/branches/users/wsanchez/deployment/twistedcaldav/xmlutil.py	2011-04-20 02:17:46 UTC (rev 7328)
@@ -0,0 +1,109 @@
+##
+# Copyright (c) 2010 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 with_statement
+
+import xml.etree.ElementTree as XML
+try:
+    from xml.etree.ElementTree import ParseError as XMLParseError
+except ImportError:
+    from xml.parsers.expat import ExpatError as XMLParseError
+
+# Utilities for working with ElementTree
+
+def readXML(xmlfile, expectedRootTag=None):
+    """
+    Read in XML data from a file and parse into ElementTree. Optionally verify
+    the root node is what we expect.
+    
+    @param xmlfile: file to read from
+    @type xmlfile: C{File}
+    @param expectedRootTag: root tag (qname) to test or C{None}
+    @type expectedRootTag: C{str}
+    @return: C{tuple} of C{ElementTree}, C{Element}
+    """
+
+    # Read in XML
+    try:
+        etree = XML.ElementTree(file=xmlfile)
+    except XMLParseError, e:
+        raise ValueError("Unable to parse file '%s' because: %s" % (xmlfile, e,))
+
+    if expectedRootTag:
+        root = etree.getroot()
+        if root.tag != expectedRootTag:
+            raise ValueError("Ignoring file '%s' because it is not a %s file" % (xmlfile, expectedRootTag,))
+    
+    return etree, etree.getroot()
+
+def elementToXML(element):
+    return XML.tostring(element)
+
+def writeXML(xmlfile, root):
+    
+    data = """<?xml version="1.0" encoding="utf-8"?>
+<!DOCTYPE %s SYSTEM "%s.dtd">
+
+""" % (root.tag, root.tag)
+
+    INDENT = 2
+
+    # Generate indentation
+    def _indentNode(node, level=0):
+        
+        if node.text is not None and node.text.strip():
+            return
+        elif len(node.getchildren()):
+            indent = "\n" + " " * (level + 1) * INDENT
+            node.text = indent
+            for child in node.getchildren():
+                child.tail = indent
+                _indentNode(child, level + 1)
+            if len(node.getchildren()):
+                node.getchildren()[-1].tail = "\n" + " " * level * INDENT
+
+    _indentNode(root, 0)
+    data += XML.tostring(root) + "\n"
+
+    with open(xmlfile, "w") as f:
+        f.write(data)
+
+def newElementTreeWithRoot(roottag):
+
+    root = createElement(roottag)
+    etree = XML.ElementTree(root)
+    
+    return etree, root
+
+def createElement(tag, text=None, **attrs):
+
+    child = XML.Element(tag, attrs)
+    child.text = text
+    return child
+
+def addSubElement(parent, tag, text=None):
+
+    child = XML.SubElement(parent, tag)
+    child.text = text
+    return child
+
+def changeSubElementText(parent, tag, text):
+    
+    child = parent.find(tag)
+    if child is not None:
+        child.text = text
+    else:
+        addSubElement(parent, tag, text)
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.macosforge.org/pipermail/calendarserver-changes/attachments/20110419/f0df0039/attachment-0001.html>


More information about the calendarserver-changes mailing list