[CalendarServer-changes] [14084] CalendarServer/trunk
source_changes at macosforge.org
source_changes at macosforge.org
Thu Oct 16 09:12:30 PDT 2014
Revision: 14084
http://trac.calendarserver.org//changeset/14084
Author: cdaboo at apple.com
Date: 2014-10-16 09:12:30 -0700 (Thu, 16 Oct 2014)
Log Message:
-----------
Cross-pod proxy support. Still needs work to better handle errors and improve performance.
Modified Paths:
--------------
CalendarServer/trunk/conf/caldavd-test-podA.plist
CalendarServer/trunk/conf/caldavd-test-podB.plist
CalendarServer/trunk/txdav/caldav/datastore/scheduling/ischedule/localservers.py
CalendarServer/trunk/txdav/caldav/datastore/scheduling/ischedule/test/test_localservers.py
CalendarServer/trunk/txdav/common/datastore/podding/conduit.py
CalendarServer/trunk/txdav/who/cache.py
CalendarServer/trunk/txdav/who/delegates.py
Added Paths:
-----------
CalendarServer/trunk/bin/testpods
CalendarServer/trunk/txdav/common/datastore/podding/attachments.py
CalendarServer/trunk/txdav/common/datastore/podding/base.py
CalendarServer/trunk/txdav/common/datastore/podding/directory.py
CalendarServer/trunk/txdav/common/datastore/podding/sharing_base.py
CalendarServer/trunk/txdav/common/datastore/podding/sharing_invites.py
CalendarServer/trunk/txdav/common/datastore/podding/sharing_store.py
Added: CalendarServer/trunk/bin/testpods
===================================================================
--- CalendarServer/trunk/bin/testpods (rev 0)
+++ CalendarServer/trunk/bin/testpods 2014-10-16 16:12:30 UTC (rev 14084)
@@ -0,0 +1,173 @@
+#!/bin/sh
+# -*- sh-basic-offset: 2 -*-
+
+##
+# Copyright (c) 2005-2014 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.
+##
+
+set -e;
+set -u;
+
+wd="$(cd "$(dirname "$0")/.." && pwd -L)";
+
+. "${wd}/bin/_build.sh";
+
+init_build > /dev/null;
+
+cdt="${py_virtualenv}/src/caldavtester";
+
+##
+# Command line handling
+##
+
+ verbose="";
+serverinfo="${cdt}/scripts/server/serverinfo-pod.xml";
+ printres="";
+ subdir="";
+ random="--random";
+ seed="";
+ ssl="";
+ cdtdebug="";
+
+usage ()
+{
+ program="$(basename "$0")";
+ echo "Usage: ${program} [-v] [-s serverinfo]";
+ echo "Options:";
+ echo " -d Set the script subdirectory";
+ echo " -h Print this help and exit";
+ echo " -o Execute tests in order";
+ echo " -r Print request and response";
+ echo " -s Set the serverinfo.xml";
+ echo " -t Set the CalDAVTester directory";
+ echo " -x Random seed to use.";
+ echo " -v Verbose.";
+ echo " -z Use SSL.";
+ echo " -D Turn on CalDAVTester debugging";
+
+ if [ "${1-}" == "-" ]; then return 0; fi;
+ exit 64;
+}
+
+while getopts 'Dhvrozt:s:d:x:' option; do
+ case "$option" in
+ '?') usage; ;;
+ 'h') usage -; exit 0; ;;
+ 't') cdt="${OPTARG}"; serverinfo="${OPTARG}/scripts/server/serverinfo-pod.xml"; ;;
+ 'd') subdir="--subdir=${OPTARG}"; ;;
+ 's') serverinfo="${OPTARG}"; ;;
+ 'r') printres="--always-print-request --always-print-response"; ;;
+ 'v') verbose="v"; ;;
+ 'o') random=""; ;;
+ 'x') seed="--random-seed ${OPTARG}"; ;;
+ 'z') ssl="--ssl"; ;;
+ 'D') cdtdebug="--debug"; ;;
+ esac;
+done;
+
+shift $((${OPTIND} - 1));
+
+if [ $# == 0 ]; then
+ set - "--all";
+fi;
+
+##
+# Do The Right Thing
+##
+
+do_setup="false";
+develop > /dev/null;
+
+# Set up sandbox
+
+sandboxdir="/tmp/cdt_server_sandbox"
+
+if [ -d "${sandboxdir}" ]; then
+ rm -rf "${sandboxdir}"
+fi;
+
+configdir="${sandboxdir}/Config"
+serverrootA="${sandboxdir}/podA"
+serverrootB="${sandboxdir}/podB"
+
+mkdir -p "${configdir}/auth"
+mkdir -p "${serverrootA}/Logs" "${serverrootA}/Run" "${serverrootA}/Data/Documents"
+mkdir -p "${serverrootB}/Logs" "${serverrootB}/Run" "${serverrootB}/Data/Documents"
+
+cp conf/caldavd-test.plist "${configdir}/caldavd-cdt.plist"
+cp conf/caldavd-test-podA.plist "${configdir}/caldavd-cdt-podA.plist"
+cp conf/caldavd-test-podB.plist "${configdir}/caldavd-cdt-podB.plist"
+cp conf/auth/proxies-test-pod.xml "${configdir}/auth/proxies-cdt.xml"
+cp conf/auth/resources-test-pod.xml "${configdir}/auth/resources-cdt.xml"
+cp conf/auth/augments-test-pod.xml "${configdir}/auth/augments-cdt.xml"
+cp conf/auth/accounts-test-pod.xml "${configdir}/auth/accounts-cdt.xml"
+
+# Modify the plists
+
+python -c "import plistlib; f=plistlib.readPlist('${configdir}/caldavd-cdt.plist'); f['ConfigRoot'] = '${configdir}'; f['RunRoot'] = 'Run'; f['Authentication']['Kerberos']['Enabled'] = False; plistlib.writePlist(f, '${configdir}/caldavd-cdt.plist');"
+python -c "import plistlib; f=plistlib.readPlist('${configdir}/caldavd-cdt-podA.plist'); f['ImportConfig'] = '${configdir}/caldavd-cdt.plist'; f['ServerRoot'] = '${serverrootA}'; f['ProxyLoadFromFile'] = '${configdir}/auth/proxies-cdt.xml'; f['ResourceService']['params']['xmlFile'] = '${configdir}/auth/resources-cdt.xml'; f['DirectoryService']['params']['xmlFile'] = '${configdir}/auth/accounts-cdt.xml'; f['AugmentService']['params']['xmlFiles'] = ['${configdir}/auth/augments-cdt.xml']; plistlib.writePlist(f, '${configdir}/caldavd-cdt-podA.plist');"
+python -c "import plistlib; f=plistlib.readPlist('${configdir}/caldavd-cdt-podB.plist'); f['ImportConfig'] = '${configdir}/caldavd-cdt.plist'; f['ServerRoot'] = '${serverrootB}'; f['ProxyLoadFromFile'] = '${configdir}/auth/proxies-cdt.xml'; f['ResourceService']['params']['xmlFile'] = '${configdir}/auth/resources-cdt.xml'; f['DirectoryService']['params']['xmlFile'] = '${configdir}/auth/accounts-cdt.xml'; f['AugmentService']['params']['xmlFiles'] = ['${configdir}/auth/augments-cdt.xml']; plistlib.writePlist(f, '${configdir}/caldavd-cdt-podB.plist');"
+
+runpod() {
+ local podsuffix="$1"; shift;
+
+ # Start the server
+
+ "${wd}/bin/run" -nd -c "${configdir}/caldavd-cdt-${podsuffix}.plist"
+
+ /bin/echo -n "Waiting for server ${podsuffix} to start up..."
+
+ while [ ! -f "${sandboxdir}/${podsuffix}/Run/caldav-instance-0.pid" ]; do
+ sleep 1
+ /bin/echo -n "."
+ done;
+
+ echo "Server ${podsuffix} has started"
+}
+
+stoppod() {
+ local podsuffix="$1"; shift;
+
+ echo "Stopping server ${podsuffix}"
+ "${wd}/bin/run" -nk -c "${configdir}/caldavd-cdt-${podsuffix}.plist"
+}
+
+runpod "podA";
+runpod "podB";
+
+# Don't exit if testcaldav.py fails, because we need to clean up afterwards.
+
+set +e
+
+# Run CDT
+
+echo "Starting CDT run"
+
+cd "${cdt}" && "${python}" testcaldav.py ${random} ${seed} ${ssl} ${cdtdebug} --print-details-onfail ${printres} -s "${serverinfo}" -x scripts/tests-pod ${subdir} "$@";
+
+# Capture exit status of testcaldav.py to use as this script's exit status.
+
+STATUS=$?
+
+# Re-enable exit on failure incase run -nk fails
+
+set -e
+
+stoppod "podA";
+stoppod "podB";
+
+# Exit with the exit status of testcaldav.py, to reflect the test suite's result
+
+exit $STATUS
Property changes on: CalendarServer/trunk/bin/testpods
___________________________________________________________________
Added: svn:executable
+ *
Modified: CalendarServer/trunk/conf/caldavd-test-podA.plist
===================================================================
--- CalendarServer/trunk/conf/caldavd-test-podA.plist 2014-10-16 16:09:16 UTC (rev 14083)
+++ CalendarServer/trunk/conf/caldavd-test-podA.plist 2014-10-16 16:12:30 UTC (rev 14084)
@@ -55,7 +55,7 @@
<key>DirectoryService</key>
<dict>
<key>type</key>
- <string>twistedcaldav.directory.xmlfile.XMLDirectoryService</string>
+ <string>xml</string>
<key>params</key>
<dict>
@@ -70,7 +70,7 @@
<key>Enabled</key>
<true/>
<key>type</key>
- <string>twistedcaldav.directory.xmlfile.XMLDirectoryService</string>
+ <string>xml</string>
<key>params</key>
<dict>
@@ -202,8 +202,6 @@
</dict>
<key>MaxClients</key>
<integer>5</integer>
- <key>memcached</key>
- <string>../memcached/_root/bin/memcached</string> <!-- Find in PATH -->
<key>Options</key>
<array>
<!--<string>-vv</string>-->
Modified: CalendarServer/trunk/conf/caldavd-test-podB.plist
===================================================================
--- CalendarServer/trunk/conf/caldavd-test-podB.plist 2014-10-16 16:09:16 UTC (rev 14083)
+++ CalendarServer/trunk/conf/caldavd-test-podB.plist 2014-10-16 16:12:30 UTC (rev 14084)
@@ -62,7 +62,7 @@
<key>DirectoryService</key>
<dict>
<key>type</key>
- <string>twistedcaldav.directory.xmlfile.XMLDirectoryService</string>
+ <string>xml</string>
<key>params</key>
<dict>
@@ -77,7 +77,7 @@
<key>Enabled</key>
<true/>
<key>type</key>
- <string>twistedcaldav.directory.xmlfile.XMLDirectoryService</string>
+ <string>xml</string>
<key>params</key>
<dict>
@@ -101,19 +101,6 @@
</dict>
</dict>
- <!-- Sqlite ProxyDB Service - must use the same one as Pod A-->
- <key>ProxyDBService</key>
- <dict>
- <key>type</key>
- <string>twistedcaldav.directory.calendaruserproxy.ProxySqliteDB</string>
-
- <key>params</key>
- <dict>
- <key>dbpath</key>
- <string>./data/podA/Data/proxies.sqlite</string>
- </dict>
- </dict>
-
<key>ProxyLoadFromFile</key>
<string>./conf/auth/proxies-test-pod.xml</string>
@@ -222,8 +209,6 @@
</dict>
<key>MaxClients</key>
<integer>5</integer>
- <key>memcached</key>
- <string>../memcached/_root/bin/memcached</string> <!-- Find in PATH -->
<key>Options</key>
<array>
<!--<string>-vv</string>-->
Modified: CalendarServer/trunk/txdav/caldav/datastore/scheduling/ischedule/localservers.py
===================================================================
--- CalendarServer/trunk/txdav/caldav/datastore/scheduling/ischedule/localservers.py 2014-10-16 16:09:16 UTC (rev 14083)
+++ CalendarServer/trunk/txdav/caldav/datastore/scheduling/ischedule/localservers.py 2014-10-16 16:12:30 UTC (rev 14084)
@@ -105,6 +105,10 @@
return self._thisServer
+ def allServersExceptThis(self):
+ return filter(lambda x: x != self._thisServer, self._servers.values())
+
+
def installReverseProxies(self, maxClients):
"""
Install a reverse proxy for each of the other servers in the "pod".
Modified: CalendarServer/trunk/txdav/caldav/datastore/scheduling/ischedule/test/test_localservers.py
===================================================================
--- CalendarServer/trunk/txdav/caldav/datastore/scheduling/ischedule/test/test_localservers.py 2014-10-16 16:09:16 UTC (rev 14083)
+++ CalendarServer/trunk/txdav/caldav/datastore/scheduling/ischedule/test/test_localservers.py 2014-10-16 16:12:30 UTC (rev 14084)
@@ -93,6 +93,7 @@
self.assertTrue(servers.getServerById("00001").thisServer)
self.assertFalse(servers.getServerById("00002").thisServer)
+ self.assertEqual(servers.getThisServer(), servers.getServerById("00001"))
self.patch(config, "ServerHostName", "caldav2.example.com")
self.patch(config, "SSLPort", 8443)
@@ -104,8 +105,30 @@
self.assertFalse(servers.getServerById("00001").thisServer)
self.assertTrue(servers.getServerById("00002").thisServer)
+ self.assertEqual(servers.getThisServer(), servers.getServerById("00002"))
+ def test_all_except_this_server(self):
+
+ servers = self._setupServers()
+
+ self.assertTrue(servers.getServerById("00001").thisServer)
+ self.assertFalse(servers.getServerById("00002").thisServer)
+ self.assertEqual(servers.allServersExceptThis(), [servers.getServerById("00002"), ])
+
+ self.patch(config, "ServerHostName", "caldav2.example.com")
+ self.patch(config, "SSLPort", 8443)
+ self.patch(config, "BindSSLPorts", [8843])
+
+ xmlFile = StringIO.StringIO(ServerTests.data1)
+ servers = ServersDB()
+ servers.load(xmlFile, ignoreIPLookupFailures=True)
+
+ self.assertFalse(servers.getServerById("00001").thisServer)
+ self.assertTrue(servers.getServerById("00002").thisServer)
+ self.assertEqual(servers.allServersExceptThis(), [servers.getServerById("00001"), ])
+
+
def test_check_this_ip(self):
servers = self._setupServers()
Added: CalendarServer/trunk/txdav/common/datastore/podding/attachments.py
===================================================================
--- CalendarServer/trunk/txdav/common/datastore/podding/attachments.py (rev 0)
+++ CalendarServer/trunk/txdav/common/datastore/podding/attachments.py 2014-10-16 16:12:30 UTC (rev 14084)
@@ -0,0 +1,204 @@
+##
+# Copyright (c) 2013-2014 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 twisted.internet.defer import inlineCallbacks, returnValue
+from twisted.python.reflect import namedClass
+
+
+class AttachmentsPoddingConduitMixin(object):
+ """
+ Defines the cross-pod API for managed attachments that will be mixed into the
+ L{PoddingConduit} class.
+ """
+
+ @inlineCallbacks
+ def send_add_attachment(self, objectResource, rids, content_type, filename, stream):
+ """
+ Managed attachment addAttachment call.
+
+ @param objectResource: child resource having an attachment added
+ @type objectResource: L{CalendarObject}
+ @param rids: list of recurrence ids
+ @type rids: C{list}
+ @param content_type: content type of attachment data
+ @type content_type: L{MimeType}
+ @param filename: name of attachment
+ @type filename: C{str}
+ @param stream: attachment data stream
+ @type stream: L{IStream}
+ """
+
+ actionName = "add-attachment"
+ shareeView = objectResource._parentCollection
+ request, recipient = yield self._getRequestForResource(actionName, shareeView, objectResource)
+ request["rids"] = rids
+ request["filename"] = filename
+
+ response = yield self.sendRequest(shareeView._txn, recipient, request, stream, content_type)
+
+ if response["result"] == "ok":
+ returnValue(response["value"])
+ elif response["result"] == "exception":
+ raise namedClass(response["class"])(response["result"])
+
+
+ @inlineCallbacks
+ def recv_add_attachment(self, txn, request):
+ """
+ Process an addAttachment cross-pod request. Request arguments as per L{send_add_attachment}.
+
+ @param request: request arguments
+ @type request: C{dict}
+ """
+
+ actionName = "add-attachment"
+ _ignore_shareeView, objectResource = yield self._getResourcesForRequest(txn, request, actionName)
+ try:
+ attachment, location = yield objectResource.addAttachment(
+ request["rids"],
+ request["streamType"],
+ request["filename"],
+ request["stream"],
+ )
+ except Exception as e:
+ returnValue({
+ "result": "exception",
+ "class": ".".join((e.__class__.__module__, e.__class__.__name__,)),
+ "request": str(e),
+ })
+
+ returnValue({
+ "result": "ok",
+ "value": (attachment.managedID(), location,),
+ })
+
+
+ @inlineCallbacks
+ def send_update_attachment(self, objectResource, managed_id, content_type, filename, stream):
+ """
+ Managed attachment updateAttachment call.
+
+ @param objectResource: child resource having an attachment added
+ @type objectResource: L{CalendarObject}
+ @param managed_id: managed-id to update
+ @type managed_id: C{str}
+ @param content_type: content type of attachment data
+ @type content_type: L{MimeType}
+ @param filename: name of attachment
+ @type filename: C{str}
+ @param stream: attachment data stream
+ @type stream: L{IStream}
+ """
+
+ actionName = "update-attachment"
+ shareeView = objectResource._parentCollection
+ request, recipient = yield self._getRequestForResource(actionName, shareeView, objectResource)
+ request["managedID"] = managed_id
+ request["filename"] = filename
+
+ response = yield self.sendRequest(shareeView._txn, recipient, request, stream, content_type)
+
+ if response["result"] == "ok":
+ returnValue(response["value"])
+ elif response["result"] == "exception":
+ raise namedClass(response["class"])(response["result"])
+
+
+ @inlineCallbacks
+ def recv_update_attachment(self, txn, request):
+ """
+ Process an updateAttachment cross-pod request. Request arguments as per L{send_update_attachment}.
+
+ @param request: request arguments
+ @type request: C{dict}
+ """
+
+ actionName = "update-attachment"
+ _ignore_shareeView, objectResource = yield self._getResourcesForRequest(txn, request, actionName)
+ try:
+ attachment, location = yield objectResource.updateAttachment(
+ request["managedID"],
+ request["streamType"],
+ request["filename"],
+ request["stream"],
+ )
+ except Exception as e:
+ returnValue({
+ "result": "exception",
+ "class": ".".join((e.__class__.__module__, e.__class__.__name__,)),
+ "request": str(e),
+ })
+
+ returnValue({
+ "result": "ok",
+ "value": (attachment.managedID(), location,),
+ })
+
+
+ @inlineCallbacks
+ def send_remove_attachment(self, objectResource, rids, managed_id):
+ """
+ Managed attachment removeAttachment call.
+
+ @param objectResource: child resource having an attachment added
+ @type objectResource: L{CalendarObject}
+ @param rids: list of recurrence ids
+ @type rids: C{list}
+ @param managed_id: managed-id to update
+ @type managed_id: C{str}
+ """
+
+ actionName = "remove-attachment"
+ shareeView = objectResource._parentCollection
+ request, recipient = yield self._getRequestForResource(actionName, shareeView, objectResource)
+ request["rids"] = rids
+ request["managedID"] = managed_id
+
+ response = yield self.sendRequest(shareeView._txn, recipient, request)
+
+ if response["result"] == "ok":
+ returnValue(response["value"])
+ elif response["result"] == "exception":
+ raise namedClass(response["class"])(response["result"])
+
+
+ @inlineCallbacks
+ def recv_remove_attachment(self, txn, request):
+ """
+ Process an removeAttachment cross-pod request. Request arguments as per L{send_remove_attachment}.
+
+ @param request: request arguments
+ @type request: C{dict}
+ """
+
+ actionName = "remove-attachment"
+ _ignore_shareeView, objectResource = yield self._getResourcesForRequest(txn, request, actionName)
+ try:
+ yield objectResource.removeAttachment(
+ request["rids"],
+ request["managedID"],
+ )
+ except Exception as e:
+ returnValue({
+ "result": "exception",
+ "class": ".".join((e.__class__.__module__, e.__class__.__name__,)),
+ "request": str(e),
+ })
+
+ returnValue({
+ "result": "ok",
+ "value": None,
+ })
Added: CalendarServer/trunk/txdav/common/datastore/podding/base.py
===================================================================
--- CalendarServer/trunk/txdav/common/datastore/podding/base.py (rev 0)
+++ CalendarServer/trunk/txdav/common/datastore/podding/base.py 2014-10-16 16:12:30 UTC (rev 14084)
@@ -0,0 +1,21 @@
+##
+# Copyright (c) 2013-2014 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.
+##
+
+class FailedCrossPodRequestError(RuntimeError):
+ """
+ Request returned an error.
+ """
+ pass
Modified: CalendarServer/trunk/txdav/common/datastore/podding/conduit.py
===================================================================
--- CalendarServer/trunk/txdav/common/datastore/podding/conduit.py 2014-10-16 16:09:16 UTC (rev 14083)
+++ CalendarServer/trunk/txdav/common/datastore/podding/conduit.py 2014-10-16 16:12:30 UTC (rev 14084)
@@ -17,29 +17,26 @@
from twext.python.log import Logger
from twisted.internet.defer import inlineCallbacks, returnValue
-from twisted.python.reflect import namedClass
-from txdav.caldav.datastore.scheduling.freebusy import generateFreeBusyInfo
-from txdav.common.datastore.podding.request import ConduitRequest
from txdav.common.idirectoryservice import DirectoryRecordNotFoundError
-from txdav.common.icommondatastore import ExternalShareFailed
+from txdav.common.datastore.podding.attachments import AttachmentsPoddingConduitMixin
+from txdav.common.datastore.podding.base import FailedCrossPodRequestError
+from txdav.common.datastore.podding.directory import DirectoryPoddingConduitMixin
+from txdav.common.datastore.podding.request import ConduitRequest
+from txdav.common.datastore.podding.sharing_invites import SharingInvitesPoddingConduitMixin
+from txdav.common.datastore.podding.sharing_store import SharingStorePoddingConduitMixin
-from twistedcaldav.caldavxml import TimeRange
-
log = Logger()
-class FailedCrossPodRequestError(RuntimeError):
+class PoddingConduit(
+ AttachmentsPoddingConduitMixin,
+ SharingInvitesPoddingConduitMixin,
+ SharingStorePoddingConduitMixin,
+ DirectoryPoddingConduitMixin,
+):
"""
- Request returned an error.
- """
- pass
-
-
-
-class PoddingConduit(object):
- """
This class is the API/RPC bridge between cross-pod requests and the store.
Each cross-pod request/response is described by a Python C{dict} that is serialized
@@ -63,8 +60,8 @@
Some simple forms of send_/recv_ methods can be auto-generated to simplify coding.
- Right now this conduit is used for cross-pod sharing operations. In the future we will
- likely use it for cross-pod migration.
+ Actual implementations of this will be done via mix-ins for the different sub-systems using
+ the conduit.
"""
conduitRequestClass = ConduitRequest
@@ -105,10 +102,14 @@
returnValue((source, destination,))
- @inlineCallbacks
def sendRequest(self, txn, recipient, data, stream=None, streamType=None):
+ return self.sendRequestToServer(txn, recipient.server(), data, stream, streamType)
- request = self.conduitRequestClass(recipient.server(), data, stream, streamType)
+
+ @inlineCallbacks
+ def sendRequestToServer(self, txn, server, data, stream=None, streamType=None):
+
+ request = self.conduitRequestClass(server, data, stream, streamType)
try:
response = (yield request.doRequest(txn))
except Exception as e:
@@ -154,691 +155,3 @@
yield txn.commit()
returnValue(result)
-
-
- #
- # Invite related apis
- #
-
- @inlineCallbacks
- def send_shareinvite(self, txn, homeType, ownerUID, ownerID, ownerName, shareeUID, shareUID, bindMode, summary, copy_properties, supported_components):
- """
- Send a sharing invite cross-pod message.
-
- @param homeType: Type of home being shared.
- @type homeType: C{int}
- @param ownerUID: UID of the sharer.
- @type ownerUID: C{str}
- @param ownerID: resource ID of the sharer calendar
- @type ownerID: C{int}
- @param ownerName: owner's name of the sharer calendar
- @type ownerName: C{str}
- @param shareeUID: UID of the sharee
- @type shareeUID: C{str}
- @param shareUID: Resource/invite ID for sharee
- @type shareUID: C{str}
- @param bindMode: bind mode for the share
- @type bindMode: C{str}
- @param summary: sharing message
- @type summary: C{str}
- @param copy_properties: C{str} name/value for properties to be copied
- @type copy_properties: C{dict}
- @param supported_components: supproted components, may be C{None}
- @type supported_components: C{str}
- """
-
- _ignore_sender, recipient = yield self.validRequest(ownerUID, shareeUID)
-
- action = {
- "action": "shareinvite",
- "type": homeType,
- "owner": ownerUID,
- "owner_id": ownerID,
- "owner_name": ownerName,
- "sharee": shareeUID,
- "share_id": shareUID,
- "mode": bindMode,
- "summary": summary,
- "properties": copy_properties,
- }
- if supported_components is not None:
- action["supported-components"] = supported_components
-
- result = yield self.sendRequest(txn, recipient, action)
- returnValue(result)
-
-
- @inlineCallbacks
- def recv_shareinvite(self, txn, message):
- """
- Process a sharing invite cross-pod message. Message arguments as per L{send_shareinvite}.
-
- @param message: message arguments
- @type message: C{dict}
- """
-
- if message["action"] != "shareinvite":
- raise FailedCrossPodRequestError("Wrong action '{}' for recv_shareinvite".format(message["action"]))
-
- # Create a share
- shareeHome = yield txn.homeWithUID(message["type"], message["sharee"], create=True)
- if shareeHome is None or shareeHome.external():
- raise FailedCrossPodRequestError("Invalid sharee UID specified")
-
- try:
- yield shareeHome.processExternalInvite(
- message["owner"],
- message["owner_id"],
- message["owner_name"],
- message["share_id"],
- message["mode"],
- message["summary"],
- message["properties"],
- supported_components=message.get("supported-components")
- )
- except ExternalShareFailed as e:
- raise FailedCrossPodRequestError(str(e))
-
- returnValue({
- "result": "ok",
- })
-
-
- @inlineCallbacks
- def send_shareuninvite(self, txn, homeType, ownerUID, ownerID, shareeUID, shareUID):
- """
- Send a sharing uninvite cross-pod message.
-
- @param homeType: Type of home being shared.
- @type homeType: C{int}
- @param ownerUID: UID of the sharer.
- @type ownerUID: C{str}
- @param ownerID: resource ID of the sharer calendar
- @type ownerID: C{int}
- @param shareeUID: UID of the sharee
- @type shareeUID: C{str}
- @param shareUID: Resource/invite ID for sharee
- @type shareUID: C{str}
- """
-
- _ignore_sender, recipient = yield self.validRequest(ownerUID, shareeUID)
-
- action = {
- "action": "shareuninvite",
- "type": homeType,
- "owner": ownerUID,
- "owner_id": ownerID,
- "sharee": shareeUID,
- "share_id": shareUID,
- }
-
- result = yield self.sendRequest(txn, recipient, action)
- returnValue(result)
-
-
- @inlineCallbacks
- def recv_shareuninvite(self, txn, message):
- """
- Process a sharing uninvite cross-pod message. Message arguments as per L{send_shareuninvite}.
-
- @param message: message arguments
- @type message: C{dict}
- """
-
- if message["action"] != "shareuninvite":
- raise FailedCrossPodRequestError("Wrong action '{}' for recv_shareuninvite".format(message["action"]))
-
- # Create a share
- shareeHome = yield txn.homeWithUID(message["type"], message["sharee"], create=True)
- if shareeHome is None or shareeHome.external():
- FailedCrossPodRequestError("Invalid sharee UID specified")
-
- try:
- yield shareeHome.processExternalUninvite(
- message["owner"],
- message["owner_id"],
- message["share_id"],
- )
- except ExternalShareFailed as e:
- FailedCrossPodRequestError(str(e))
-
- returnValue({
- "result": "ok",
- })
-
-
- @inlineCallbacks
- def send_sharereply(self, txn, homeType, ownerUID, shareeUID, shareUID, bindStatus, summary=None):
- """
- Send a sharing reply cross-pod message.
-
- @param homeType: Type of home being shared.
- @type homeType: C{int}
- @param ownerUID: UID of the sharer.
- @type ownerUID: C{str}
- @param shareeUID: UID of the recipient
- @type shareeUID: C{str}
- @param shareUID: Resource/invite ID for recipient
- @type shareUID: C{str}
- @param bindStatus: bind mode for the share
- @type bindStatus: C{str}
- @param summary: sharing message
- @type summary: C{str}
- """
-
- _ignore_sender, recipient = yield self.validRequest(shareeUID, ownerUID)
-
- action = {
- "action": "sharereply",
- "type": homeType,
- "owner": ownerUID,
- "sharee": shareeUID,
- "share_id": shareUID,
- "status": bindStatus,
- }
- if summary is not None:
- action["summary"] = summary
-
- result = yield self.sendRequest(txn, recipient, action)
- returnValue(result)
-
-
- @inlineCallbacks
- def recv_sharereply(self, txn, message):
- """
- Process a sharing reply cross-pod message. Message arguments as per L{send_sharereply}.
-
- @param message: message arguments
- @type message: C{dict}
- """
-
- if message["action"] != "sharereply":
- raise FailedCrossPodRequestError("Wrong action '{}' for recv_sharereply".format(message["action"]))
-
- # Create a share
- ownerHome = yield txn.homeWithUID(message["type"], message["owner"])
- if ownerHome is None or ownerHome.external():
- FailedCrossPodRequestError("Invalid owner UID specified")
-
- try:
- yield ownerHome.processExternalReply(
- message["owner"],
- message["sharee"],
- message["share_id"],
- message["status"],
- summary=message.get("summary")
- )
- except ExternalShareFailed as e:
- FailedCrossPodRequestError(str(e))
-
- returnValue({
- "result": "ok",
- })
-
-
- #
- # Managed attachment related apis
- #
-
- @inlineCallbacks
- def send_add_attachment(self, objectResource, rids, content_type, filename, stream):
- """
- Managed attachment addAttachment call.
-
- @param objectResource: child resource having an attachment added
- @type objectResource: L{CalendarObject}
- @param rids: list of recurrence ids
- @type rids: C{list}
- @param content_type: content type of attachment data
- @type content_type: L{MimeType}
- @param filename: name of attachment
- @type filename: C{str}
- @param stream: attachment data stream
- @type stream: L{IStream}
- """
-
- actionName = "add-attachment"
- shareeView = objectResource._parentCollection
- action, recipient = yield self._send(actionName, shareeView, objectResource)
- action["rids"] = rids
- action["filename"] = filename
- result = yield self.sendRequest(shareeView._txn, recipient, action, stream, content_type)
- if result["result"] == "ok":
- returnValue(result["value"])
- elif result["result"] == "exception":
- raise namedClass(result["class"])(result["message"])
-
-
- @inlineCallbacks
- def recv_add_attachment(self, txn, message):
- """
- Process an addAttachment cross-pod message. Message arguments as per L{send_add_attachment}.
-
- @param message: message arguments
- @type message: C{dict}
- """
-
- actionName = "add-attachment"
- _ignore_shareeView, objectResource = yield self._recv(txn, message, actionName)
- try:
- attachment, location = yield objectResource.addAttachment(
- message["rids"],
- message["streamType"],
- message["filename"],
- message["stream"],
- )
- except Exception as e:
- returnValue({
- "result": "exception",
- "class": ".".join((e.__class__.__module__, e.__class__.__name__,)),
- "message": str(e),
- })
-
- returnValue({
- "result": "ok",
- "value": (attachment.managedID(), location,),
- })
-
-
- @inlineCallbacks
- def send_update_attachment(self, objectResource, managed_id, content_type, filename, stream):
- """
- Managed attachment updateAttachment call.
-
- @param objectResource: child resource having an attachment added
- @type objectResource: L{CalendarObject}
- @param managed_id: managed-id to update
- @type managed_id: C{str}
- @param content_type: content type of attachment data
- @type content_type: L{MimeType}
- @param filename: name of attachment
- @type filename: C{str}
- @param stream: attachment data stream
- @type stream: L{IStream}
- """
-
- actionName = "update-attachment"
- shareeView = objectResource._parentCollection
- action, recipient = yield self._send(actionName, shareeView, objectResource)
- action["managedID"] = managed_id
- action["filename"] = filename
- result = yield self.sendRequest(shareeView._txn, recipient, action, stream, content_type)
- if result["result"] == "ok":
- returnValue(result["value"])
- elif result["result"] == "exception":
- raise namedClass(result["class"])(result["message"])
-
-
- @inlineCallbacks
- def recv_update_attachment(self, txn, message):
- """
- Process an updateAttachment cross-pod message. Message arguments as per L{send_update_attachment}.
-
- @param message: message arguments
- @type message: C{dict}
- """
-
- actionName = "update-attachment"
- _ignore_shareeView, objectResource = yield self._recv(txn, message, actionName)
- try:
- attachment, location = yield objectResource.updateAttachment(
- message["managedID"],
- message["streamType"],
- message["filename"],
- message["stream"],
- )
- except Exception as e:
- returnValue({
- "result": "exception",
- "class": ".".join((e.__class__.__module__, e.__class__.__name__,)),
- "message": str(e),
- })
-
- returnValue({
- "result": "ok",
- "value": (attachment.managedID(), location,),
- })
-
-
- @inlineCallbacks
- def send_remove_attachment(self, objectResource, rids, managed_id):
- """
- Managed attachment removeAttachment call.
-
- @param objectResource: child resource having an attachment added
- @type objectResource: L{CalendarObject}
- @param rids: list of recurrence ids
- @type rids: C{list}
- @param managed_id: managed-id to update
- @type managed_id: C{str}
- """
-
- actionName = "remove-attachment"
- shareeView = objectResource._parentCollection
- action, recipient = yield self._send(actionName, shareeView, objectResource)
- action["rids"] = rids
- action["managedID"] = managed_id
- result = yield self.sendRequest(shareeView._txn, recipient, action)
- if result["result"] == "ok":
- returnValue(result["value"])
- elif result["result"] == "exception":
- raise namedClass(result["class"])(result["message"])
-
-
- @inlineCallbacks
- def recv_remove_attachment(self, txn, message):
- """
- Process an removeAttachment cross-pod message. Message arguments as per L{send_remove_attachment}.
-
- @param message: message arguments
- @type message: C{dict}
- """
-
- actionName = "remove-attachment"
- _ignore_shareeView, objectResource = yield self._recv(txn, message, actionName)
- try:
- yield objectResource.removeAttachment(
- message["rids"],
- message["managedID"],
- )
- except Exception as e:
- returnValue({
- "result": "exception",
- "class": ".".join((e.__class__.__module__, e.__class__.__name__,)),
- "message": str(e),
- })
-
- returnValue({
- "result": "ok",
- "value": None,
- })
-
-
- #
- # Sharer data access related apis
- #
-
- @inlineCallbacks
- def _send(self, action, parent, child=None):
- """
- Base behavior for an operation on a L{CommonHomeChild}.
-
- @param shareeView: sharee resource being operated on.
- @type shareeView: L{CommonHomeChildExternal}
- """
-
- homeType = parent.ownerHome()._homeType
- ownerUID = parent.ownerHome().uid()
- ownerID = parent.external_id()
- shareeUID = parent.viewerHome().uid()
-
- _ignore_sender, recipient = yield self.validRequest(shareeUID, ownerUID)
-
- result = {
- "action": action,
- "type": homeType,
- "owner": ownerUID,
- "owner_id": ownerID,
- "sharee": shareeUID,
- }
- if child is not None:
- result["resource_id"] = child.id()
- returnValue((result, recipient))
-
-
- @inlineCallbacks
- def _recv(self, txn, message, expected_action):
- """
- Base behavior for sharer data access.
-
- @param message: message arguments
- @type message: C{dict}
- """
-
- if message["action"] != expected_action:
- raise FailedCrossPodRequestError("Wrong action '{}' for recv_{}".format(message["action"], expected_action))
-
- # Get a share
- ownerHome = yield txn.homeWithUID(message["type"], message["owner"])
- if ownerHome is None or ownerHome.external():
- FailedCrossPodRequestError("Invalid owner UID specified")
-
- shareeHome = yield txn.homeWithUID(message["type"], message["sharee"])
- if shareeHome is None or not shareeHome.external():
- FailedCrossPodRequestError("Invalid sharee UID specified")
-
- shareeView = yield shareeHome.childWithID(message["owner_id"])
- if shareeView is None:
- FailedCrossPodRequestError("Invalid shared resource specified")
-
- resourceID = message.get("resource_id", None)
- if resourceID is not None:
- objectResource = yield shareeView.objectResourceWithID(resourceID)
- if objectResource is None:
- FailedCrossPodRequestError("Invalid owner shared object resource specified")
- else:
- objectResource = None
-
- returnValue((shareeView, objectResource,))
-
-
- #
- # Simple calls are ones where there is no argument and a single return value. We can simplify
- # code generation for these by dynamically generating the appropriate class methods.
- #
-
- @inlineCallbacks
- def _simple_send(self, actionName, shareeView, objectResource=None, transform=None, args=None, kwargs=None):
- """
- A simple send operation that returns a value.
-
- @param actionName: name of the action.
- @type actionName: C{str}
- @param shareeView: sharee resource being operated on.
- @type shareeView: L{CommonHomeChildExternal}
- @param objectResource: the resource being operated on, or C{None} for classmethod.
- @type objectResource: L{CommonObjectResourceExternal}
- @param transform: a function used to convert the JSON result into return values.
- @type transform: C{callable}
- @param args: list of optional arguments.
- @type args: C{list}
- @param kwargs: optional keyword arguments.
- @type kwargs: C{dict}
- """
-
- action, recipient = yield self._send(actionName, shareeView, objectResource)
- if args is not None:
- action["arguments"] = args
- if kwargs is not None:
- action["keywords"] = kwargs
- result = yield self.sendRequest(shareeView._txn, recipient, action)
- if result["result"] == "ok":
- returnValue(result["value"] if transform is None else transform(result["value"], shareeView, objectResource))
- elif result["result"] == "exception":
- raise namedClass(result["class"])(result["message"])
-
-
- @inlineCallbacks
- def _simple_recv(self, txn, actionName, message, method, onHomeChild=True, transform=None):
- """
- A simple recv operation that returns a value. We also look for an optional set of arguments/keywords
- and include those only if present.
-
- @param actionName: name of the action.
- @type actionName: C{str}
- @param message: message arguments
- @type message: C{dict}
- @param method: name of the method to execute on the shared resource to get the result.
- @type method: C{str}
- @param transform: method to call on returned JSON value to convert it to something useful.
- @type transform: C{callable}
- """
-
- shareeView, objectResource = yield self._recv(txn, message, actionName)
- try:
- if onHomeChild:
- # Operate on the L{CommonHomeChild}
- value = yield getattr(shareeView, method)(*message.get("arguments", ()), **message.get("keywords", {}))
- else:
- # Operate on the L{CommonObjectResource}
- if objectResource is not None:
- value = yield getattr(objectResource, method)(*message.get("arguments", ()), **message.get("keywords", {}))
- else:
- # classmethod call
- value = yield getattr(shareeView._objectResourceClass, method)(shareeView, *message.get("arguments", ()), **message.get("keywords", {}))
- except Exception as e:
- returnValue({
- "result": "exception",
- "class": ".".join((e.__class__.__module__, e.__class__.__name__,)),
- "message": str(e),
- })
-
- returnValue({
- "result": "ok",
- "value": transform(value, shareeView, objectResource) if transform is not None else value,
- })
-
-
- @inlineCallbacks
- def send_freebusy(
- self,
- calresource,
- timerange,
- matchtotal,
- excludeuid,
- organizer,
- organizerPrincipal,
- same_calendar_user,
- servertoserver,
- event_details,
- ):
- action, recipient = yield self._send("freebusy", calresource)
- action["timerange"] = [timerange.start.getText(), timerange.end.getText()]
- action["matchtotal"] = matchtotal
- action["excludeuid"] = excludeuid
- action["organizer"] = organizer
- action["organizerPrincipal"] = organizerPrincipal
- action["same_calendar_user"] = same_calendar_user
- action["servertoserver"] = servertoserver
- action["event_details"] = event_details
- result = yield self.sendRequest(calresource._txn, recipient, action)
- if result["result"] == "ok":
- returnValue((result["fbresults"], result["matchtotal"],))
- elif result["result"] == "exception":
- raise namedClass(result["class"])(result["message"])
-
-
- @inlineCallbacks
- def recv_freebusy(self, txn, message):
- """
- Process a freebusy cross-pod message. Message arguments as per L{send_freebusy}.
-
- @param message: message arguments
- @type message: C{dict}
- """
-
- shareeView, _ignore_objectResource = yield self._recv(txn, message, "freebusy")
- try:
- # Operate on the L{CommonHomeChild}
- fbinfo = [[], [], []]
- matchtotal = yield generateFreeBusyInfo(
- shareeView,
- fbinfo,
- TimeRange(start=message["timerange"][0], end=message["timerange"][1]),
- message["matchtotal"],
- message["excludeuid"],
- message["organizer"],
- message["organizerPrincipal"],
- message["same_calendar_user"],
- message["servertoserver"],
- message["event_details"],
- logItems=None
- )
- except Exception as e:
- returnValue({
- "result": "exception",
- "class": ".".join((e.__class__.__module__, e.__class__.__name__,)),
- "message": str(e),
- })
-
- for i in range(3):
- for j in range(len(fbinfo[i])):
- fbinfo[i][j] = fbinfo[i][j].getText()
-
- returnValue({
- "result": "ok",
- "fbresults": fbinfo,
- "matchtotal": matchtotal,
- })
-
-
- @staticmethod
- def _to_tuple(value, shareeView, objectResource):
- return tuple(value)
-
-
- @staticmethod
- def _to_string(value, shareeView, objectResource):
- return str(value)
-
-
- @staticmethod
- def _to_externalize(value, shareeView, objectResource):
- if isinstance(value, shareeView._objectResourceClass):
- value = value.externalize()
- elif value is not None:
- value = [v.externalize() for v in value]
- return value
-
-
- @classmethod
- def _make_simple_homechild_action(cls, action, method, transform_recv=None, transform_send=None):
- setattr(
- cls,
- "send_{}".format(action),
- lambda self, shareeView, *args, **kwargs:
- self._simple_send(action, shareeView, transform=transform_send, args=args, kwargs=kwargs)
- )
- setattr(
- cls,
- "recv_{}".format(action),
- lambda self, txn, message:
- self._simple_recv(txn, action, message, method, transform=transform_recv)
- )
-
-
- @classmethod
- def _make_simple_object_action(cls, action, method, transform_recv=None, transform_send=None):
- setattr(
- cls,
- "send_{}".format(action),
- lambda self, shareeView, objectResource, *args, **kwargs:
- self._simple_send(action, shareeView, objectResource, transform=transform_send, args=args, kwargs=kwargs)
- )
- setattr(
- cls,
- "recv_{}".format(action),
- lambda self, txn, message:
- self._simple_recv(txn, action, message, method, onHomeChild=False, transform=transform_recv)
- )
-
-
-# Calls on L{CommonHomeChild} objects
-PoddingConduit._make_simple_homechild_action("countobjects", "countObjectResources")
-PoddingConduit._make_simple_homechild_action("listobjects", "listObjectResources")
-PoddingConduit._make_simple_homechild_action("resourceuidforname", "resourceUIDForName")
-PoddingConduit._make_simple_homechild_action("resourcenameforuid", "resourceNameForUID")
-PoddingConduit._make_simple_homechild_action("movehere", "moveObjectResourceHere")
-PoddingConduit._make_simple_homechild_action("moveaway", "moveObjectResourceAway")
-PoddingConduit._make_simple_homechild_action("synctoken", "syncToken")
-PoddingConduit._make_simple_homechild_action("resourcenamessincerevision", "resourceNamesSinceRevision", transform_send=PoddingConduit._to_tuple)
-PoddingConduit._make_simple_homechild_action("search", "search")
-
-# Calls on L{CommonObjectResource} objects
-PoddingConduit._make_simple_object_action("loadallobjects", "loadAllObjects", transform_recv=PoddingConduit._to_externalize)
-PoddingConduit._make_simple_object_action("loadallobjectswithnames", "loadAllObjectsWithNames", transform_recv=PoddingConduit._to_externalize)
-PoddingConduit._make_simple_object_action("objectwith", "objectWith", transform_recv=PoddingConduit._to_externalize)
-PoddingConduit._make_simple_object_action("create", "create", transform_recv=PoddingConduit._to_externalize)
-PoddingConduit._make_simple_object_action("setcomponent", "setComponent")
-PoddingConduit._make_simple_object_action("component", "component", transform_recv=PoddingConduit._to_string)
-PoddingConduit._make_simple_object_action("remove", "remove")
Added: CalendarServer/trunk/txdav/common/datastore/podding/directory.py
===================================================================
--- CalendarServer/trunk/txdav/common/datastore/podding/directory.py (rev 0)
+++ CalendarServer/trunk/txdav/common/datastore/podding/directory.py 2014-10-16 16:12:30 UTC (rev 14084)
@@ -0,0 +1,215 @@
+##
+# Copyright (c) 2013-2014 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 twisted.internet.defer import inlineCallbacks, returnValue
+from twisted.python.reflect import namedClass
+from txdav.common.datastore.podding.base import FailedCrossPodRequestError
+from txdav.who.delegates import _delegatesOfUIDs, _delegatedToUIDs, setDelegates
+
+
+class DirectoryPoddingConduitMixin(object):
+ """
+ Defines the cross-pod API for common directory operations that will be mixed into the
+ L{PoddingConduit} class.
+ """
+
+ @inlineCallbacks
+ def send_set_delegates(self, txn, delegator, delegates, readWrite):
+ """
+ Set delegates for delegator on another pod.
+
+ @param txn: transaction to use
+ @type txn: L{CommonStoreTransaction}
+ @param delegator: delegator to set
+ @type delegator: L{DirectoryRecord}
+ @param delegates: delegates to set
+ @type delegates: L{list} of L{DirectoryRecord}
+ @param readWrite: if True, read and write access delegates are returned;
+ read-only access otherwise
+ """
+ if delegator.thisServer():
+ raise FailedCrossPodRequestError("Cross-pod destination on this server: {}".format(delegator.uid))
+
+ request = {
+ "action": "set-delegates",
+ "uid": delegator.uid,
+ "delegates": [delegate.uid for delegate in delegates],
+ "read-write": readWrite,
+ }
+ response = yield self.sendRequestToServer(txn, delegator.server(), request)
+
+ if response["result"] == "ok":
+ returnValue(None)
+ elif response["result"] == "exception":
+ raise namedClass(response["class"])(response["result"])
+
+
+ @inlineCallbacks
+ def recv_set_delegates(self, txn, request):
+ """
+ Process a set delegates cross-pod request. Request arguments as per L{send_set_delegates}.
+
+ @param request: request arguments
+ @type request: C{dict}
+ """
+
+ if request["action"] != "set-delegates":
+ raise FailedCrossPodRequestError("Wrong action '{}' for recv_set_delegates".format(request["action"]))
+
+ try:
+ delegator = yield txn.directoryService().recordWithUID(request["uid"])
+ if delegator is None or not delegator.thisServer():
+ raise FailedCrossPodRequestError("Cross-pod delegate not on this server: {}".format(delegator.uid))
+
+ delegates = []
+ for uid in request["delegates"]:
+ delegate = yield txn.directoryService().recordWithUID(uid)
+ if delegate is None:
+ raise FailedCrossPodRequestError("Cross-pod delegate missing on this server: {}".format(uid))
+ delegates.append(delegate)
+
+ yield setDelegates(txn, delegator, delegates, request["read-write"])
+ except Exception as e:
+ returnValue({
+ "result": "exception",
+ "class": ".".join((e.__class__.__module__, e.__class__.__name__,)),
+ "request": str(e),
+ })
+
+ returnValue({
+ "result": "ok",
+ })
+
+
+ @inlineCallbacks
+ def send_get_delegates(self, txn, delegator, readWrite, expanded=False):
+ """
+ Get delegates from another pod.
+
+ @param txn: transaction to use
+ @type txn: L{CommonStoreTransaction}
+ @param delegator: delegator to lookup
+ @type delegator: L{DirectoryRecord}
+ @param readWrite: if True, read and write access delegates are returned;
+ read-only access otherwise
+ """
+ if delegator.thisServer():
+ raise FailedCrossPodRequestError("Cross-pod destination on this server: {}".format(delegator.uid))
+
+ request = {
+ "action": "get-delegates",
+ "uid": delegator.uid,
+ "read-write": readWrite,
+ "expanded": expanded,
+ }
+ response = yield self.sendRequestToServer(txn, delegator.server(), request)
+
+ if response["result"] == "ok":
+ returnValue(set(response["value"]))
+ elif response["result"] == "exception":
+ raise namedClass(response["class"])(response["result"])
+
+
+ @inlineCallbacks
+ def recv_get_delegates(self, txn, request):
+ """
+ Process an delegates cross-pod request. Request arguments as per L{send_get_delegates}.
+
+ @param request: request arguments
+ @type request: C{dict}
+ """
+
+ if request["action"] != "get-delegates":
+ raise FailedCrossPodRequestError("Wrong action '{}' for recv_get_delegates".format(request["action"]))
+
+ try:
+ delegator = yield txn.directoryService().recordWithUID(request["uid"])
+ if delegator is None or not delegator.thisServer():
+ raise FailedCrossPodRequestError("Cross-pod delegate not on this server: {}".format(delegator.uid))
+
+ delegates = yield _delegatesOfUIDs(txn, delegator, request["read-write"], request["expanded"])
+ except Exception as e:
+ returnValue({
+ "result": "exception",
+ "class": ".".join((e.__class__.__module__, e.__class__.__name__,)),
+ "request": str(e),
+ })
+
+ returnValue({
+ "result": "ok",
+ "value": list(delegates),
+ })
+
+
+ @inlineCallbacks
+ def send_get_delegators(self, txn, server, delegate, readWrite):
+ """
+ Get delegators from another pod.
+
+ @param txn: transaction to use
+ @type txn: L{CommonStoreTransaction}
+ @param server: server to query
+ @type server: L{Server}
+ @param delegate: delegate to lookup
+ @type delegate: L{DirectoryRecord}
+ @param readWrite: if True, read and write access delegates are returned;
+ read-only access otherwise
+ """
+ if not delegate.thisServer():
+ raise FailedCrossPodRequestError("Cross-pod destination on this server: {}".format(delegate.uid))
+
+ request = {
+ "action": "get-delegators",
+ "uid": delegate.uid,
+ "read-write": readWrite,
+ }
+ response = yield self.sendRequestToServer(txn, server, request)
+
+ if response["result"] == "ok":
+ returnValue(set(response["value"]))
+ elif response["result"] == "exception":
+ raise namedClass(response["class"])(response["result"])
+
+
+ @inlineCallbacks
+ def recv_get_delegators(self, txn, request):
+ """
+ Process an delegators cross-pod request. Request arguments as per L{send_get_delegators}.
+
+ @param request: request arguments
+ @type request: C{dict}
+ """
+
+ if request["action"] != "get-delegators":
+ raise FailedCrossPodRequestError("Wrong action '{}' for recv_get_delegators".format(request["action"]))
+
+ try:
+ delegate = yield txn.directoryService().recordWithUID(request["uid"])
+ if delegate is None or delegate.thisServer():
+ raise FailedCrossPodRequestError("Cross-pod delegate missing or on this server: {}".format(delegate.uid))
+
+ delegateors = yield _delegatedToUIDs(txn, delegate, request["read-write"], onlyThisServer=True)
+ except Exception as e:
+ returnValue({
+ "result": "exception",
+ "class": ".".join((e.__class__.__module__, e.__class__.__name__,)),
+ "request": str(e),
+ })
+
+ returnValue({
+ "result": "ok",
+ "value": list(delegateors),
+ })
Added: CalendarServer/trunk/txdav/common/datastore/podding/sharing_base.py
===================================================================
--- CalendarServer/trunk/txdav/common/datastore/podding/sharing_base.py (rev 0)
+++ CalendarServer/trunk/txdav/common/datastore/podding/sharing_base.py 2014-10-16 16:12:30 UTC (rev 14084)
@@ -0,0 +1,94 @@
+##
+# Copyright (c) 2013-2014 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 twisted.internet.defer import inlineCallbacks, returnValue
+
+from txdav.common.datastore.podding.base import FailedCrossPodRequestError
+
+
+class SharingCommonPoddingConduit(object):
+ """
+ Defines common cross-pod API for sharing that will be mixed into the L{PoddingConduit} class.
+ """
+
+ #
+ # Sharer data access related apis
+ #
+
+ @inlineCallbacks
+ def _getRequestForResource(self, action, parent, child=None):
+ """
+ Create a request for an operation on a L{CommonHomeChild}. This is used when building the JSON
+ request object prior to sending it.
+
+ @param shareeView: sharee resource being operated on.
+ @type shareeView: L{CommonHomeChildExternal}
+ """
+
+ homeType = parent.ownerHome()._homeType
+ ownerUID = parent.ownerHome().uid()
+ ownerID = parent.external_id()
+ shareeUID = parent.viewerHome().uid()
+
+ _ignore_sender, recipient = yield self.validRequest(shareeUID, ownerUID)
+
+ result = {
+ "action": action,
+ "type": homeType,
+ "owner": ownerUID,
+ "owner_id": ownerID,
+ "sharee": shareeUID,
+ }
+ if child is not None:
+ result["resource_id"] = child.id()
+ returnValue((result, recipient))
+
+
+ @inlineCallbacks
+ def _getResourcesForRequest(self, txn, request, expected_action):
+ """
+ Find the resources associated with the request. This is used when a JSON request has been received
+ and the underlying store objects the request refers to need to be found.
+
+ @param request: request arguments
+ @type request: C{dict}
+ """
+
+ if request["action"] != expected_action:
+ raise FailedCrossPodRequestError("Wrong action '{}' for recv_{}".format(request["action"], expected_action))
+
+ # Get a share
+ ownerHome = yield txn.homeWithUID(request["type"], request["owner"])
+ if ownerHome is None or ownerHome.external():
+ FailedCrossPodRequestError("Invalid owner UID specified")
+
+ shareeHome = yield txn.homeWithUID(request["type"], request["sharee"])
+ if shareeHome is None or not shareeHome.external():
+ FailedCrossPodRequestError("Invalid sharee UID specified")
+
+ shareeView = yield shareeHome.childWithID(request["owner_id"])
+ if shareeView is None:
+ FailedCrossPodRequestError("Invalid shared resource specified")
+
+ resourceID = request.get("resource_id", None)
+ if resourceID is not None:
+ objectResource = yield shareeView.objectResourceWithID(resourceID)
+ if objectResource is None:
+ FailedCrossPodRequestError("Invalid owner shared object resource specified")
+ else:
+ objectResource = None
+
+ returnValue((shareeView, objectResource,))
Added: CalendarServer/trunk/txdav/common/datastore/podding/sharing_invites.py
===================================================================
--- CalendarServer/trunk/txdav/common/datastore/podding/sharing_invites.py (rev 0)
+++ CalendarServer/trunk/txdav/common/datastore/podding/sharing_invites.py 2014-10-16 16:12:30 UTC (rev 14084)
@@ -0,0 +1,246 @@
+##
+# Copyright (c) 2013-2014 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 twisted.internet.defer import inlineCallbacks, returnValue
+
+from txdav.common.icommondatastore import ExternalShareFailed
+from txdav.common.datastore.podding.base import FailedCrossPodRequestError
+from txdav.common.datastore.podding.sharing_base import SharingCommonPoddingConduit
+
+
+class SharingInvitesPoddingConduitMixin(SharingCommonPoddingConduit):
+ """
+ Defines the cross-pod API for sharing invites that will be mixed into the
+ L{PoddingConduit} class.
+ """
+
+ @inlineCallbacks
+ def send_shareinvite(self, txn, homeType, ownerUID, ownerID, ownerName, shareeUID, shareUID, bindMode, summary, copy_properties, supported_components):
+ """
+ Send a sharing invite cross-pod message.
+
+ @param homeType: Type of home being shared.
+ @type homeType: C{int}
+ @param ownerUID: UID of the sharer.
+ @type ownerUID: C{str}
+ @param ownerID: resource ID of the sharer calendar
+ @type ownerID: C{int}
+ @param ownerName: owner's name of the sharer calendar
+ @type ownerName: C{str}
+ @param shareeUID: UID of the sharee
+ @type shareeUID: C{str}
+ @param shareUID: Resource/invite ID for sharee
+ @type shareUID: C{str}
+ @param bindMode: bind mode for the share
+ @type bindMode: C{str}
+ @param summary: sharing message
+ @type summary: C{str}
+ @param copy_properties: C{str} name/value for properties to be copied
+ @type copy_properties: C{dict}
+ @param supported_components: supproted components, may be C{None}
+ @type supported_components: C{str}
+ """
+
+ _ignore_sender, recipient = yield self.validRequest(ownerUID, shareeUID)
+
+ request = {
+ "action": "shareinvite",
+ "type": homeType,
+ "owner": ownerUID,
+ "owner_id": ownerID,
+ "owner_name": ownerName,
+ "sharee": shareeUID,
+ "share_id": shareUID,
+ "mode": bindMode,
+ "summary": summary,
+ "properties": copy_properties,
+ }
+ if supported_components is not None:
+ request["supported-components"] = supported_components
+
+ result = yield self.sendRequest(txn, recipient, request)
+ returnValue(result)
+
+
+ @inlineCallbacks
+ def recv_shareinvite(self, txn, request):
+ """
+ Process a sharing invite cross-pod request. Request arguments as per L{send_shareinvite}.
+
+ @param request: request arguments
+ @type request: C{dict}
+ """
+
+ if request["action"] != "shareinvite":
+ raise FailedCrossPodRequestError("Wrong action '{}' for recv_shareinvite".format(request["action"]))
+
+ # Sharee home on this pod must exist (create if needed)
+ shareeHome = yield txn.homeWithUID(request["type"], request["sharee"], create=True)
+ if shareeHome is None or shareeHome.external():
+ raise FailedCrossPodRequestError("Invalid sharee UID specified")
+
+ # Create a share
+ try:
+ yield shareeHome.processExternalInvite(
+ request["owner"],
+ request["owner_id"],
+ request["owner_name"],
+ request["share_id"],
+ request["mode"],
+ request["summary"],
+ request["properties"],
+ supported_components=request.get("supported-components")
+ )
+ except ExternalShareFailed as e:
+ raise FailedCrossPodRequestError(str(e))
+
+ returnValue({
+ "result": "ok",
+ })
+
+
+ @inlineCallbacks
+ def send_shareuninvite(self, txn, homeType, ownerUID, ownerID, shareeUID, shareUID):
+ """
+ Send a sharing uninvite cross-pod message.
+
+ @param homeType: Type of home being shared.
+ @type homeType: C{int}
+ @param ownerUID: UID of the sharer.
+ @type ownerUID: C{str}
+ @param ownerID: resource ID of the sharer calendar
+ @type ownerID: C{int}
+ @param shareeUID: UID of the sharee
+ @type shareeUID: C{str}
+ @param shareUID: Resource/invite ID for sharee
+ @type shareUID: C{str}
+ """
+
+ _ignore_sender, recipient = yield self.validRequest(ownerUID, shareeUID)
+
+ request = {
+ "action": "shareuninvite",
+ "type": homeType,
+ "owner": ownerUID,
+ "owner_id": ownerID,
+ "sharee": shareeUID,
+ "share_id": shareUID,
+ }
+
+ result = yield self.sendRequest(txn, recipient, request)
+ returnValue(result)
+
+
+ @inlineCallbacks
+ def recv_shareuninvite(self, txn, request):
+ """
+ Process a sharing uninvite cross-pod request. Request arguments as per L{send_shareuninvite}.
+
+ @param request: request arguments
+ @type request: C{dict}
+ """
+
+ if request["action"] != "shareuninvite":
+ raise FailedCrossPodRequestError("Wrong action '{}' for recv_shareuninvite".format(request["action"]))
+
+ # Sharee home on this pod must already exist
+ shareeHome = yield txn.homeWithUID(request["type"], request["sharee"])
+ if shareeHome is None or shareeHome.external():
+ FailedCrossPodRequestError("Invalid sharee UID specified")
+
+ # Remove a share
+ try:
+ yield shareeHome.processExternalUninvite(
+ request["owner"],
+ request["owner_id"],
+ request["share_id"],
+ )
+ except ExternalShareFailed as e:
+ FailedCrossPodRequestError(str(e))
+
+ returnValue({
+ "result": "ok",
+ })
+
+
+ @inlineCallbacks
+ def send_sharereply(self, txn, homeType, ownerUID, shareeUID, shareUID, bindStatus, summary=None):
+ """
+ Send a sharing reply cross-pod message.
+
+ @param homeType: Type of home being shared.
+ @type homeType: C{int}
+ @param ownerUID: UID of the sharer.
+ @type ownerUID: C{str}
+ @param shareeUID: UID of the recipient
+ @type shareeUID: C{str}
+ @param shareUID: Resource/invite ID for recipient
+ @type shareUID: C{str}
+ @param bindStatus: bind mode for the share
+ @type bindStatus: C{str}
+ @param summary: sharing message
+ @type summary: C{str}
+ """
+
+ _ignore_sender, recipient = yield self.validRequest(shareeUID, ownerUID)
+
+ request = {
+ "action": "sharereply",
+ "type": homeType,
+ "owner": ownerUID,
+ "sharee": shareeUID,
+ "share_id": shareUID,
+ "status": bindStatus,
+ }
+ if summary is not None:
+ request["summary"] = summary
+
+ result = yield self.sendRequest(txn, recipient, request)
+ returnValue(result)
+
+
+ @inlineCallbacks
+ def recv_sharereply(self, txn, request):
+ """
+ Process a sharing reply cross-pod request. Request arguments as per L{send_sharereply}.
+
+ @param request: request arguments
+ @type request: C{dict}
+ """
+
+ if request["action"] != "sharereply":
+ raise FailedCrossPodRequestError("Wrong action '{}' for recv_sharereply".format(request["action"]))
+
+ # Sharer home on this pod must already exist
+ ownerHome = yield txn.homeWithUID(request["type"], request["owner"])
+ if ownerHome is None or ownerHome.external():
+ FailedCrossPodRequestError("Invalid owner UID specified")
+
+ # Process a reply
+ try:
+ yield ownerHome.processExternalReply(
+ request["owner"],
+ request["sharee"],
+ request["share_id"],
+ request["status"],
+ summary=request.get("summary")
+ )
+ except ExternalShareFailed as e:
+ FailedCrossPodRequestError(str(e))
+
+ returnValue({
+ "result": "ok",
+ })
Added: CalendarServer/trunk/txdav/common/datastore/podding/sharing_store.py
===================================================================
--- CalendarServer/trunk/txdav/common/datastore/podding/sharing_store.py (rev 0)
+++ CalendarServer/trunk/txdav/common/datastore/podding/sharing_store.py 2014-10-16 16:12:30 UTC (rev 14084)
@@ -0,0 +1,268 @@
+##
+# Copyright (c) 2013-2014 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 twisted.internet.defer import inlineCallbacks, returnValue
+from twisted.python.reflect import namedClass
+
+from txdav.common.datastore.podding.sharing_base import SharingCommonPoddingConduit
+from txdav.caldav.datastore.scheduling.freebusy import generateFreeBusyInfo
+
+from twistedcaldav.caldavxml import TimeRange
+
+
+class SharingStorePoddingConduitMixin(SharingCommonPoddingConduit):
+ """
+ Defines the cross-pod API for access to shared resource data that will be mixed into the
+ L{PoddingConduit} class.
+ """
+
+ #
+ # Simple calls are ones where there is no argument and a single return value. We can simplify
+ # code generation for these by dynamically generating the appropriate class methods.
+ #
+
+ @inlineCallbacks
+ def _simple_send(self, actionName, shareeView, objectResource=None, transform=None, args=None, kwargs=None):
+ """
+ A simple send operation that returns a value.
+
+ @param actionName: name of the action.
+ @type actionName: C{str}
+ @param shareeView: sharee resource being operated on.
+ @type shareeView: L{CommonHomeChildExternal}
+ @param objectResource: the resource being operated on, or C{None} for classmethod.
+ @type objectResource: L{CommonObjectResourceExternal}
+ @param transform: a function used to convert the JSON response into return values.
+ @type transform: C{callable}
+ @param args: list of optional arguments.
+ @type args: C{list}
+ @param kwargs: optional keyword arguments.
+ @type kwargs: C{dict}
+ """
+
+ request, recipient = yield self._getRequestForResource(actionName, shareeView, objectResource)
+ if args is not None:
+ request["arguments"] = args
+ if kwargs is not None:
+ request["keywords"] = kwargs
+ response = yield self.sendRequest(shareeView._txn, recipient, request)
+ if response["result"] == "ok":
+ returnValue(response["value"] if transform is None else transform(response["value"], shareeView, objectResource))
+ elif response["result"] == "exception":
+ raise namedClass(response["class"])(response["result"])
+
+
+ @inlineCallbacks
+ def _simple_recv(self, txn, actionName, request, method, onHomeChild=True, transform=None):
+ """
+ A simple recv operation that returns a value. We also look for an optional set of arguments/keywords
+ and include those only if present.
+
+ @param actionName: name of the action.
+ @type actionName: C{str}
+ @param request: request arguments
+ @type request: C{dict}
+ @param method: name of the method to execute on the shared resource to get the result.
+ @type method: C{str}
+ @param transform: method to call on returned JSON value to convert it to something useful.
+ @type transform: C{callable}
+ """
+
+ shareeView, objectResource = yield self._getResourcesForRequest(txn, request, actionName)
+ try:
+ if onHomeChild:
+ # Operate on the L{CommonHomeChild}
+ value = yield getattr(shareeView, method)(*request.get("arguments", ()), **request.get("keywords", {}))
+ else:
+ # Operate on the L{CommonObjectResource}
+ if objectResource is not None:
+ value = yield getattr(objectResource, method)(*request.get("arguments", ()), **request.get("keywords", {}))
+ else:
+ # classmethod call
+ value = yield getattr(shareeView._objectResourceClass, method)(shareeView, *request.get("arguments", ()), **request.get("keywords", {}))
+ except Exception as e:
+ returnValue({
+ "result": "exception",
+ "class": ".".join((e.__class__.__module__, e.__class__.__name__,)),
+ "request": str(e),
+ })
+
+ returnValue({
+ "result": "ok",
+ "value": transform(value, shareeView, objectResource) if transform is not None else value,
+ })
+
+
+ @inlineCallbacks
+ def send_freebusy(
+ self,
+ calresource,
+ timerange,
+ matchtotal,
+ excludeuid,
+ organizer,
+ organizerPrincipal,
+ same_calendar_user,
+ servertoserver,
+ event_details,
+ ):
+ """
+ Request free busy information for a shared calendar collection hosted on a different pod. See
+ L{txdav.caldav.datastore.scheduling.freebusy} for the base free busy lookup behavior.
+ """
+ action, recipient = yield self._getRequestForResource("freebusy", calresource)
+ action["timerange"] = [timerange.start.getText(), timerange.end.getText()]
+ action["matchtotal"] = matchtotal
+ action["excludeuid"] = excludeuid
+ action["organizer"] = organizer
+ action["organizerPrincipal"] = organizerPrincipal
+ action["same_calendar_user"] = same_calendar_user
+ action["servertoserver"] = servertoserver
+ action["event_details"] = event_details
+
+ response = yield self.sendRequest(calresource._txn, recipient, action)
+
+ if response["result"] == "ok":
+ returnValue((response["fbresults"], response["matchtotal"],))
+ elif response["result"] == "exception":
+ raise namedClass(response["class"])(response["result"])
+
+
+ @inlineCallbacks
+ def recv_freebusy(self, txn, request):
+ """
+ Process a freebusy cross-pod request. Message arguments as per L{send_freebusy}.
+
+ @param request: request arguments
+ @type request: C{dict}
+ """
+
+ shareeView, _ignore_objectResource = yield self._getResourcesForRequest(txn, request, "freebusy")
+ try:
+ # Operate on the L{CommonHomeChild}
+ fbinfo = [[], [], []]
+ matchtotal = yield generateFreeBusyInfo(
+ shareeView,
+ fbinfo,
+ TimeRange(start=request["timerange"][0], end=request["timerange"][1]),
+ request["matchtotal"],
+ request["excludeuid"],
+ request["organizer"],
+ request["organizerPrincipal"],
+ request["same_calendar_user"],
+ request["servertoserver"],
+ request["event_details"],
+ logItems=None
+ )
+ except Exception as e:
+ returnValue({
+ "result": "exception",
+ "class": ".".join((e.__class__.__module__, e.__class__.__name__,)),
+ "request": str(e),
+ })
+
+ # Convert L{DateTime} objects to text for JSON response
+ for i in range(3):
+ for j in range(len(fbinfo[i])):
+ fbinfo[i][j] = fbinfo[i][j].getText()
+
+ returnValue({
+ "result": "ok",
+ "fbresults": fbinfo,
+ "matchtotal": matchtotal,
+ })
+
+
+ #
+ # Methods used to transform arguments to or results from a JSON request or response.
+ #
+ @staticmethod
+ def _to_tuple(value, shareeView, objectResource):
+ return tuple(value)
+
+
+ @staticmethod
+ def _to_string(value, shareeView, objectResource):
+ return str(value)
+
+
+ @staticmethod
+ def _to_externalize(value, shareeView, objectResource):
+ """
+ Convert the value to the external (JSON-based) representation.
+ """
+ if isinstance(value, shareeView._objectResourceClass):
+ value = value.externalize()
+ elif value is not None:
+ value = [v.externalize() for v in value]
+ return value
+
+
+ #
+ # Factory methods for binding actions to the conduit class
+ #
+ @classmethod
+ def _make_simple_homechild_action(cls, action, method, transform_recv=None, transform_send=None):
+ setattr(
+ cls,
+ "send_{}".format(action),
+ lambda self, shareeView, *args, **kwargs:
+ self._simple_send(action, shareeView, transform=transform_send, args=args, kwargs=kwargs)
+ )
+ setattr(
+ cls,
+ "recv_{}".format(action),
+ lambda self, txn, message:
+ self._simple_recv(txn, action, message, method, transform=transform_recv)
+ )
+
+
+ @classmethod
+ def _make_simple_object_action(cls, action, method, transform_recv=None, transform_send=None):
+ setattr(
+ cls,
+ "send_{}".format(action),
+ lambda self, shareeView, objectResource, *args, **kwargs:
+ self._simple_send(action, shareeView, objectResource, transform=transform_send, args=args, kwargs=kwargs)
+ )
+ setattr(
+ cls,
+ "recv_{}".format(action),
+ lambda self, txn, message:
+ self._simple_recv(txn, action, message, method, onHomeChild=False, transform=transform_recv)
+ )
+
+# These are the actions on store objects we need to expose via the conduit api
+
+# Calls on L{CommonHomeChild} objects
+SharingStorePoddingConduitMixin._make_simple_homechild_action("countobjects", "countObjectResources")
+SharingStorePoddingConduitMixin._make_simple_homechild_action("listobjects", "listObjectResources")
+SharingStorePoddingConduitMixin._make_simple_homechild_action("resourceuidforname", "resourceUIDForName")
+SharingStorePoddingConduitMixin._make_simple_homechild_action("resourcenameforuid", "resourceNameForUID")
+SharingStorePoddingConduitMixin._make_simple_homechild_action("movehere", "moveObjectResourceHere")
+SharingStorePoddingConduitMixin._make_simple_homechild_action("moveaway", "moveObjectResourceAway")
+SharingStorePoddingConduitMixin._make_simple_homechild_action("synctoken", "syncToken")
+SharingStorePoddingConduitMixin._make_simple_homechild_action("resourcenamessincerevision", "resourceNamesSinceRevision", transform_send=SharingStorePoddingConduitMixin._to_tuple)
+SharingStorePoddingConduitMixin._make_simple_homechild_action("search", "search")
+
+# Calls on L{CommonObjectResource} objects
+SharingStorePoddingConduitMixin._make_simple_object_action("loadallobjects", "loadAllObjects", transform_recv=SharingStorePoddingConduitMixin._to_externalize)
+SharingStorePoddingConduitMixin._make_simple_object_action("loadallobjectswithnames", "loadAllObjectsWithNames", transform_recv=SharingStorePoddingConduitMixin._to_externalize)
+SharingStorePoddingConduitMixin._make_simple_object_action("objectwith", "objectWith", transform_recv=SharingStorePoddingConduitMixin._to_externalize)
+SharingStorePoddingConduitMixin._make_simple_object_action("create", "create", transform_recv=SharingStorePoddingConduitMixin._to_externalize)
+SharingStorePoddingConduitMixin._make_simple_object_action("setcomponent", "setComponent")
+SharingStorePoddingConduitMixin._make_simple_object_action("component", "component", transform_recv=SharingStorePoddingConduitMixin._to_string)
+SharingStorePoddingConduitMixin._make_simple_object_action("remove", "remove")
Modified: CalendarServer/trunk/txdav/who/cache.py
===================================================================
--- CalendarServer/trunk/txdav/who/cache.py 2014-10-16 16:09:16 UTC (rev 14083)
+++ CalendarServer/trunk/txdav/who/cache.py 2014-10-16 16:12:30 UTC (rev 14084)
@@ -82,6 +82,7 @@
def __init__(self, directory, expireSeconds=30):
BaseDirectoryService.__init__(self, directory.realmName)
self._directory = directory
+ self.serversDB = directory.serversDB
self._directoryTiming = hasattr(self._directory, "_addTiming")
self._expireSeconds = expireSeconds
self.resetCache()
Modified: CalendarServer/trunk/txdav/who/delegates.py
===================================================================
--- CalendarServer/trunk/txdav/who/delegates.py 2014-10-16 16:09:16 UTC (rev 14083)
+++ CalendarServer/trunk/txdav/who/delegates.py 2014-10-16 16:12:30 UTC (rev 14084)
@@ -20,8 +20,11 @@
"""
from twisted.python.constants import Names, NamedConstant
-from twisted.internet.defer import inlineCallbacks, returnValue, succeed
+from twisted.internet.defer import inlineCallbacks, returnValue, succeed, \
+ DeferredList
+from twistedcaldav.config import config
+
from twext.python.log import Logger
from twext.who.idirectory import (
RecordType as BaseRecordType, FieldName, NotAllowedError
@@ -67,6 +70,7 @@
this record.
"""
parentUID, _ignore_proxyType = self.uid.split(u"#")
+ parentRecord = yield self.service._masterDirectory.recordWithUID(parentUID)
@inlineCallbacks
def _members(txn):
@@ -74,15 +78,11 @@
RecordType.readDelegateGroup, RecordType.writeDelegateGroup
): # Members are delegates of this record
readWrite = (self.recordType is RecordType.writeDelegateGroup)
- delegateUIDs = (
- yield txn.delegates(parentUID, readWrite, expanded=expanded)
- )
+ delegateUIDs = yield _delegatesOfUIDs(txn, parentRecord, readWrite, expanded=expanded)
else: # Members have delegated to this record
readWrite = (self.recordType is RecordType.writeDelegatorGroup)
- delegateUIDs = (
- yield txn.delegators(parentUID, readWrite)
- )
+ delegateUIDs = yield _delegatedToUIDs(txn, parentRecord, readWrite)
returnValue(delegateUIDs)
delegateUIDs = yield self.service._store.inTransaction(
@@ -121,18 +121,13 @@
m=[r.uid for r in memberRecords]
)
- @inlineCallbacks
+ delegator = (
+ yield self.service._masterDirectory.recordWithUID(parentUID)
+ )
+
def _setMembers(txn):
- yield txn.removeDelegates(parentUID, readWrite)
- yield txn.removeDelegateGroups(parentUID, readWrite)
+ return setDelegates(txn, delegator, memberRecords, readWrite)
- delegator = (
- yield self.service._masterDirectory.recordWithUID(parentUID)
- )
-
- for delegate in memberRecords:
- yield addDelegate(txn, delegator, delegate, readWrite)
-
yield self.service._store.inTransaction(
"DirectoryRecord.setMembers", _setMembers
)
@@ -229,6 +224,32 @@
@inlineCallbacks
+def setDelegates(txn, delegator, delegates, readWrite):
+ """
+ Sets the full set of delegates for a delegator.
+
+ We need to take multiple pods into account by re-directing this request
+ to the cross-pod conduit if the delegator is not local to this pod.
+
+ @param delegator: the delegator's directory record
+ @type delegator: L{IDirectoryRecord}
+ @param delegates: the delegates directory records
+ @type delegates: L{list}} of L{IDirectoryRecord}
+ @param readWrite: if True, read and write access is granted; read-only
+ access otherwise
+ """
+ if delegator.thisServer():
+ yield txn.removeDelegates(delegator.uid, readWrite)
+ yield txn.removeDelegateGroups(delegator.uid, readWrite)
+
+ for delegate in delegates:
+ yield addDelegate(txn, delegator, delegate, readWrite)
+ else:
+ yield _podSetDelegates(txn, delegator, delegates, readWrite)
+
+
+
+ at inlineCallbacks
def addDelegate(txn, delegator, delegate, readWrite):
"""
Adds "delegate" as a delegate of "delegator". The type of access is
@@ -295,11 +316,10 @@
@return: the set of directory records
@rtype: a Deferred which fires a set of L{IDirectoryRecord}
"""
+ delegateUIDs = yield _delegatesOfUIDs(txn, delegator, readWrite, expanded)
+
records = []
directory = delegator.service
- delegateUIDs = (
- yield txn.delegates(delegator.uid, readWrite, expanded=expanded)
- )
for uid in delegateUIDs:
if uid != delegator.uid:
record = (yield directory.recordWithUID(uid))
@@ -322,12 +342,126 @@
@return: the set of directory records
@rtype: a Deferred which fires a set of L{IDirectoryRecord}
"""
+ delegatorUIDs = yield _delegatedToUIDs(txn, delegate, readWrite)
+
records = []
directory = delegate.service
- delegatorUIDs = (yield txn.delegators(delegate.uid, readWrite))
for uid in delegatorUIDs:
if uid != delegate.uid:
record = (yield directory.recordWithUID(uid))
if record is not None:
records.append(record)
returnValue(records)
+
+
+
+ at inlineCallbacks
+def _delegatesOfUIDs(txn, delegator, readWrite, expanded=False):
+ """
+ Return the UIDs of the delegates of "delegator". The type of access
+ is specified by the "readWrite" parameter.
+
+ We need to take multiple pods into account by re-directing this request
+ to the cross-pod conduit if the delegator is not local to this pod.
+
+ @param delegator: the delegator's directory record
+ @type delegator: L{IDirectoryRecord}
+ @param readWrite: if True, read and write access delegates are returned;
+ read-only access otherwise
+ @return: the set of directory record uids
+ @rtype: a Deferred which fires a set of L{str}
+ """
+
+ if delegator.thisServer():
+ delegateUIDs = yield txn.delegates(delegator.uid, readWrite, expanded=expanded)
+ else:
+ delegateUIDs = yield _podDelegates(txn, delegator, readWrite, expanded=expanded)
+ returnValue(delegateUIDs)
+
+
+
+ at inlineCallbacks
+def _delegatedToUIDs(txn, delegate, readWrite, onlyThisServer=False):
+ """
+ Return the UIDs of those who have delegated to "delegate". The type of
+ access is specified by the "readWrite" parameter.
+
+ We need to take multiple pods into account by re-directing this request
+ to the cross-pod conduit if the delegate is not local to this pod.
+
+ @param delegate: the delegate's directory record
+ @type delegate: L{IDirectoryRecord}
+ @param readWrite: if True, read and write access delegators are returned;
+ read-only access otherwise
+ @param onlyThisServer: used when doing the query as part of a cross-pod request since that
+ should only returns results for this server
+ @type onlyThisServer: L{bool}
+ @return: the set of directory record uids
+ @rtype: a Deferred which fires a set of L{str}
+ """
+
+
+ delegatorUIDs = (yield txn.delegators(delegate.uid, readWrite))
+ if not onlyThisServer and config.Servers.Enabled:
+ delegatorUIDs.update((yield _podDelegators(txn, delegate, readWrite)))
+ returnValue(delegatorUIDs)
+
+
+
+def _podSetDelegates(txn, delegator, delegates, readWrite):
+ """
+ Sets the full set of delegates for a delegator.
+
+ We need to take multiple pods into account by re-directing this request
+ to the cross-pod conduit if the delegator is not local to this pod.
+
+ @param delegator: the delegator's directory record
+ @type delegator: L{IDirectoryRecord}
+ @param delegates: the delegates directory records
+ @type delegates: L{list}} of L{IDirectoryRecord}
+ @param readWrite: if True, read and write access is granted; read-only
+ access otherwise
+ """
+ return txn.store().conduit.send_set_delegates(txn, delegator, delegates, readWrite)
+
+
+
+def _podDelegates(txn, delegator, readWrite, expanded=False):
+ """
+ Do a cross-pod request to get the delegates for this delegator.
+
+ @param delegator: the delegator's directory record
+ @type delegator: L{IDirectoryRecord}
+ @param readWrite: if True, read and write access delegates are returned;
+ read-only access otherwise
+ @return: the set of directory record uids
+ @rtype: a Deferred which fires a set of L{str}
+ """
+
+ return txn.store().conduit.send_get_delegates(txn, delegator, readWrite, expanded)
+
+
+
+ at inlineCallbacks
+def _podDelegators(txn, delegate, readWrite):
+ """
+ Do a cross-pod request to get the delegators for this delegate. We need to iterate over all
+ other pod servers to get results from each one.
+
+ @param delegate: the delegate's directory record
+ @type delegate: L{IDirectoryRecord}
+ @param readWrite: if True, read and write access delegates are returned;
+ read-only access otherwise
+ @return: the set of directory record uids
+ @rtype: a Deferred which fires a set of L{str}
+ """
+
+ results = yield DeferredList([
+ txn.store().conduit.send_get_delegators(txn, server, delegate, readWrite) for
+ server in txn.directoryService().serversDB.allServersExceptThis()
+ ], consumeErrors=True)
+ delegators = set()
+ for result in results:
+ if result and result[0]:
+ delegators.update(result[1])
+ returnValue(delegators)
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://lists.macosforge.org/pipermail/calendarserver-changes/attachments/20141016/9e9ef2b6/attachment-0001.html>
More information about the calendarserver-changes
mailing list