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

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

<h3>Log Message</h3>
<pre>Make cross-pod requests more robust in the face of connectivity and data sync errors.</pre>

<h3>Modified Paths</h3>
<ul>
<li><a href="#CalendarServertrunktwistedcaldavresourcepy">CalendarServer/trunk/twistedcaldav/resource.py</a></li>
<li><a href="#CalendarServertrunktwistedcaldavstorebridgepy">CalendarServer/trunk/twistedcaldav/storebridge.py</a></li>
<li><a href="#CalendarServertrunktxdavcaldavdatastoretesttest_sql_externalpy">CalendarServer/trunk/txdav/caldav/datastore/test/test_sql_external.py</a></li>
<li><a href="#CalendarServertrunktxdavcommondatastorepoddingsharing_invitespy">CalendarServer/trunk/txdav/common/datastore/podding/sharing_invites.py</a></li>
<li><a href="#CalendarServertrunktxdavcommondatastorepoddingtestutilpy">CalendarServer/trunk/txdav/common/datastore/podding/test/util.py</a></li>
<li><a href="#CalendarServertrunktxdavcommondatastorepoddingutilpy">CalendarServer/trunk/txdav/common/datastore/podding/util.py</a></li>
<li><a href="#CalendarServertrunktxdavcommondatastoresql_externalpy">CalendarServer/trunk/txdav/common/datastore/sql_external.py</a></li>
<li><a href="#CalendarServertrunktxdavcommondatastoresql_sharingpy">CalendarServer/trunk/txdav/common/datastore/sql_sharing.py</a></li>
</ul>

</div>
<div id="patch">
<h3>Diff</h3>
<a id="CalendarServertrunktwistedcaldavresourcepy"></a>
<div class="modfile"><h4>Modified: CalendarServer/trunk/twistedcaldav/resource.py (15538 => 15539)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/trunk/twistedcaldav/resource.py        2016-04-21 19:48:22 UTC (rev 15538)
+++ CalendarServer/trunk/twistedcaldav/resource.py        2016-04-21 20:11:28 UTC (rev 15539)
</span><span class="lines">@@ -57,7 +57,7 @@
</span><span class="cx"> 
</span><span class="cx"> from txdav.caldav.datastore.util import normalizationLookup
</span><span class="cx"> from txdav.common.icommondatastore import InternalDataStoreError, \
</span><del>-    SyncTokenValidException
</del><ins>+    SyncTokenValidException, ExternalShareFailed
</ins><span class="cx"> from txdav.xml import element
</span><span class="cx"> from txdav.xml.element import dav_namespace
</span><span class="cx"> 
</span><span class="lines">@@ -357,6 +357,12 @@
</span><span class="cx">             response = yield super(CalDAVResource, self).renderHTTP(request)
</span><span class="cx">         except AlreadyFinishedError:
</span><span class="cx">             self._transactionError = True
</span><ins>+        except ExternalShareFailed:
+            # This happens when an external share is no longer valid and has been fixed
+            # by removing it from this pod. We need to treat this as a 503 &quot;error&quot; but let
+            # the transaction commit
+            self._transactionError = False
+            response = StatusResponse(responsecode.SERVICE_UNAVAILABLE, &quot;Shared collection not valid - removing.&quot;)
</ins><span class="cx">         if transaction is None:
</span><span class="cx">             transaction = self._associatedTransaction
</span><span class="cx">         if transaction is not None:
</span></span></pre></div>
<a id="CalendarServertrunktwistedcaldavstorebridgepy"></a>
<div class="modfile"><h4>Modified: CalendarServer/trunk/twistedcaldav/storebridge.py (15538 => 15539)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/trunk/twistedcaldav/storebridge.py        2016-04-21 19:48:22 UTC (rev 15538)
+++ CalendarServer/trunk/twistedcaldav/storebridge.py        2016-04-21 20:11:28 UTC (rev 15539)
</span><span class="lines">@@ -63,6 +63,7 @@
</span><span class="cx"> from txdav.carddav.iaddressbookstore import (
</span><span class="cx">     KindChangeNotAllowedError, GroupWithUnsharedAddressNotAllowedError
</span><span class="cx"> )
</span><ins>+from txdav.common.datastore.podding.base import FailedCrossPodRequestError
</ins><span class="cx"> from txdav.common.datastore.sql_tables import (
</span><span class="cx">     _BIND_MODE_READ, _BIND_MODE_WRITE,
</span><span class="cx">     _BIND_MODE_DIRECT, _BIND_STATUS_ACCEPTED
</span><span class="lines">@@ -204,8 +205,102 @@
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx"> 
</span><del>-class _CommonHomeChildCollectionMixin(object):
</del><ins>+class _CommonStoreExceptionHandler(object):
</ins><span class="cx">     &quot;&quot;&quot;
</span><ins>+    A mix-in class that is used to help trap store exceptions and turn them into
+    appropriate HTTP errors.
+
+    The class properties define mappings from a store exception type to a L{tuple} whose
+    first item is one of the class methods defined in this mix-in, and whose second argument
+    is the L{arg} passed to the class method. In some cases the second L{tuple} item will not
+    be present, and instead the argument will be directly provided to the class method.
+    &quot;&quot;&quot;
+
+    # The following are used to map store exceptions into HTTP error responses
+    StoreExceptionsErrors = {}
+    StoreMoveExceptionsErrors = {}
+
+    @classmethod
+    def _storeExceptionStatus(cls, err, arg):
+        &quot;&quot;&quot;
+        Raise a status error.
+
+        @param err: the actual exception that caused the error
+        @type err: L{Exception}
+        @param arg: description of error or C{None}
+        @type arg: C{str} or C{None}
+        &quot;&quot;&quot;
+        raise HTTPError(StatusResponse(responsecode.FORBIDDEN, arg if arg is not None else str(err)))
+
+
+    @classmethod
+    def _storeExceptionError(cls, err, arg):
+        &quot;&quot;&quot;
+        Raise a DAV:error error with the supplied error element.
+
+        @param err: the actual exception that caused the error
+        @type err: L{Exception}
+        @param arg: the error element
+        @type arg: C{tuple}
+        &quot;&quot;&quot;
+        raise HTTPError(ErrorResponse(
+            responsecode.FORBIDDEN,
+            arg,
+            str(err),
+        ))
+
+
+    @classmethod
+    def _storeExceptionUnavailable(cls, err, arg):
+        &quot;&quot;&quot;
+        Raise a service unavailable error.
+
+        @param err: the actual exception that caused the error
+        @type err: L{Exception}
+        @param arg: description of error or C{None}
+        @type arg: C{str} or C{None}
+        &quot;&quot;&quot;
+        raise HTTPError(StatusResponse(responsecode.SERVICE_UNAVAILABLE, arg if arg is not None else str(err)))
+
+
+    @classmethod
+    def _handleStoreException(cls, ex, exceptionMap):
+        &quot;&quot;&quot;
+        Process a store exception and see if it is in the supplied mapping. If so, execute the
+        method in the mapping (which will raise an HTTPError).
+
+        @param ex: the store exception that was raised
+        @type ex: L{Exception}
+        @param exceptionMap: the store exception mapping to use
+        @type exceptionMap: L{dict}
+        &quot;&quot;&quot;
+        if type(ex) in exceptionMap:
+            error, arg = exceptionMap[type(ex)]
+            error(ex, arg)
+
+
+    @classmethod
+    def _handleStoreExceptionArg(cls, ex, exceptionMap, arg):
+        &quot;&quot;&quot;
+        Process a store exception and see if it is in the supplied mapping. If so, execute the
+        method in the mapping (which will raise an HTTPError). This method is used when the argument
+        to the class method needs to be provided at runtime, rather than statically.
+
+        @param ex: the store exception that was raised
+        @type ex: L{Exception}
+        @param exceptionSet: the store exception set to use
+        @type exceptionSet: L{set}
+        @param arg: the argument to use
+        @type arg: L{object}
+        &quot;&quot;&quot;
+        if type(ex) in exceptionMap:
+            error = exceptionMap[type(ex)]
+            error(ex, arg)
+
+
+
+class _CommonHomeChildCollectionMixin(_CommonStoreExceptionHandler):
+    &quot;&quot;&quot;
</ins><span class="cx">     Methods for things which are like calendars.
</span><span class="cx">     &quot;&quot;&quot;
</span><span class="cx"> 
</span><span class="lines">@@ -446,7 +541,11 @@
</span><span class="cx">             )
</span><span class="cx">             log.error(msg)
</span><span class="cx">             raise HTTPError(StatusResponse(BAD_REQUEST, msg))
</span><del>-        response = (yield self.storeRemove(request))
</del><ins>+        try:
+            response = (yield self.storeRemove(request))
+        except Exception as err:
+            self._handleStoreException(err, self.StoreExceptionsErrors)
+            raise
</ins><span class="cx">         returnValue(response)
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="lines">@@ -1052,6 +1151,12 @@
</span><span class="cx">     Wrapper around a L{txdav.caldav.icalendar.ICalendar}.
</span><span class="cx">     &quot;&quot;&quot;
</span><span class="cx"> 
</span><ins>+    StoreExceptionsErrors = {
+        LockTimeout: (_CommonStoreExceptionHandler._storeExceptionUnavailable, &quot;Lock timed out.&quot;,),
+        AlreadyInTrashError: (_CommonStoreExceptionHandler._storeExceptionError, (calendarserver_namespace, &quot;not-in-trash&quot;,),),
+        FailedCrossPodRequestError: (_CommonStoreExceptionHandler._storeExceptionUnavailable, &quot;Cross-pod request failed.&quot;,),
+    }
+
</ins><span class="cx">     def __init__(self, calendar, home, name=None, *args, **kw):
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         Create a CalendarCollectionResource from a L{txdav.caldav.icalendar.ICalendar}
</span><span class="lines">@@ -2204,7 +2309,7 @@
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx"> 
</span><del>-class _CommonObjectResource(_NewStoreFileMetaDataHelper, CalDAVResource, FancyEqMixin):
</del><ins>+class _CommonObjectResource(_NewStoreFileMetaDataHelper, _CommonStoreExceptionHandler, CalDAVResource, FancyEqMixin):
</ins><span class="cx"> 
</span><span class="cx">     _componentFromStream = None
</span><span class="cx"> 
</span><span class="lines">@@ -2323,54 +2428,7 @@
</span><span class="cx"> 
</span><span class="cx">         returnValue(response)
</span><span class="cx"> 
</span><del>-    # The following are used to map store exceptions into HTTP error responses
-    StoreExceptionsErrors = {}
-    StoreMoveExceptionsErrors = {}
-    StoreRemoveExceptionsErrors = {}
</del><span class="cx"> 
</span><del>-    @classmethod
-    def _storeExceptionStatus(cls, err, arg):
-        &quot;&quot;&quot;
-        Raise a status error.
-
-        @param err: the actual exception that caused the error
-        @type err: L{Exception}
-        @param arg: description of error or C{None}
-        @type arg: C{str} or C{None}
-        &quot;&quot;&quot;
-        raise HTTPError(StatusResponse(responsecode.FORBIDDEN, arg if arg is not None else str(err)))
-
-
-    @classmethod
-    def _storeExceptionError(cls, err, arg):
-        &quot;&quot;&quot;
-        Raise a DAV:error error with the supplied error element.
-
-        @param err: the actual exception that caused the error
-        @type err: L{Exception}
-        @param arg: the error element
-        @type arg: C{tuple}
-        &quot;&quot;&quot;
-        raise HTTPError(ErrorResponse(
-            responsecode.FORBIDDEN,
-            arg,
-            str(err),
-        ))
-
-
-    @classmethod
-    def _storeExceptionUnavailable(cls, err, arg):
-        &quot;&quot;&quot;
-        Raise a service unavailable error.
-
-        @param err: the actual exception that caused the error
-        @type err: L{Exception}
-        @param arg: description of error or C{None}
-        @type arg: C{str} or C{None}
-        &quot;&quot;&quot;
-        raise HTTPError(StatusResponse(responsecode.SERVICE_UNAVAILABLE, arg if arg is not None else str(err)))
-
-
</del><span class="cx">     @requiresPermissions(fromParent=[davxml.Unbind()])
</span><span class="cx">     def http_DELETE(self, request):
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="lines">@@ -2459,18 +2517,10 @@
</span><span class="cx"> 
</span><span class="cx">         # Handle the various store errors
</span><span class="cx">         except Exception as err:
</span><ins>+            self._handleStoreException(err, self.StoreMoveExceptionsErrors)
+            raise
</ins><span class="cx"> 
</span><del>-            # Grab the current exception state here so we can use it in a re-raise - we need this because
-            # an inlineCallback might be called and that raises an exception when it returns, wiping out the
-            # original exception &quot;context&quot;.
-            if type(err) in self.StoreMoveExceptionsErrors:
-                error, arg = self.StoreMoveExceptionsErrors[type(err)]
-                error(err, arg)
-            else:
-                # Return the original failure (exception) state
-                raise
</del><span class="cx"> 
</span><del>-
</del><span class="cx">     def http_PROPPATCH(self, request):
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         No dead properties allowed on object resources.
</span><span class="lines">@@ -2508,11 +2558,8 @@
</span><span class="cx"> 
</span><span class="cx">         # Map store exception to HTTP errors
</span><span class="cx">         except Exception as err:
</span><del>-            if type(err) in self.StoreExceptionsErrors:
-                error, arg = self.StoreExceptionsErrors[type(err)]
-                error(err, arg)
-            else:
-                raise
</del><ins>+            self._handleStoreException(err, self.StoreExceptionsErrors)
+            raise
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     @inlineCallbacks
</span><span class="lines">@@ -2557,11 +2604,8 @@
</span><span class="cx"> 
</span><span class="cx">         # Map store exception to HTTP errors
</span><span class="cx">         except Exception as err:
</span><del>-            if type(err) in self.StoreExceptionsErrors:
-                error, arg = self.StoreExceptionsErrors[type(err)]
-                error(err, arg)
-            else:
-                raise
</del><ins>+            self._handleStoreException(err, self.StoreExceptionsErrors)
+            raise
</ins><span class="cx"> 
</span><span class="cx">         # Re-initialize to get stuff setup again now we have no object
</span><span class="cx">         self._initializeWithObject(None, self._newStoreParent)
</span><span class="lines">@@ -2698,52 +2742,49 @@
</span><span class="cx">             return False
</span><span class="cx"> 
</span><span class="cx">     StoreExceptionsErrors = {
</span><del>-        ObjectResourceNameNotAllowedError: (_CommonObjectResource._storeExceptionStatus, None,),
-        ObjectResourceNameAlreadyExistsError: (_CommonObjectResource._storeExceptionStatus, None,),
-        TooManyObjectResourcesError: (_CommonObjectResource._storeExceptionError, customxml.MaxResources(),),
-        ObjectResourceTooBigError: (_CommonObjectResource._storeExceptionError, (caldav_namespace, &quot;max-resource-size&quot;),),
-        InvalidObjectResourceError: (_CommonObjectResource._storeExceptionError, (caldav_namespace, &quot;valid-calendar-data&quot;),),
-        InvalidComponentForStoreError: (_CommonObjectResource._storeExceptionError, (caldav_namespace, &quot;valid-calendar-object-resource&quot;),),
-        InvalidComponentTypeError: (_CommonObjectResource._storeExceptionError, (caldav_namespace, &quot;supported-calendar-component&quot;),),
-        TooManyAttendeesError: (_CommonObjectResource._storeExceptionError, MaxAttendeesPerInstance.fromString(str(config.MaxAttendeesPerInstance)),),
-        InvalidCalendarAccessError: (_CommonObjectResource._storeExceptionError, (calendarserver_namespace, &quot;valid-access-restriction&quot;),),
-        ValidOrganizerError: (_CommonObjectResource._storeExceptionError, (calendarserver_namespace, &quot;valid-organizer&quot;),),
-        UIDExistsError: (_CommonObjectResource._storeExceptionError, NoUIDConflict(),),
-        UIDExistsElsewhereError: (_CommonObjectResource._storeExceptionError, (caldav_namespace, &quot;unique-scheduling-object-resource&quot;),),
-        InvalidUIDError: (_CommonObjectResource._storeExceptionError, NoUIDConflict(),),
-        InvalidPerUserDataMerge: (_CommonObjectResource._storeExceptionError, (caldav_namespace, &quot;valid-calendar-data&quot;),),
-        AttendeeAllowedError: (_CommonObjectResource._storeExceptionError, (caldav_namespace, &quot;attendee-allowed&quot;),),
-        InvalidOverriddenInstanceError: (_CommonObjectResource._storeExceptionError, (caldav_namespace, &quot;valid-calendar-data&quot;),),
-        TooManyInstancesError: (_CommonObjectResource._storeExceptionError, MaxInstances.fromString(str(config.MaxAllowedInstances)),),
-        AttachmentStoreValidManagedID: (_CommonObjectResource._storeExceptionError, (caldav_namespace, &quot;valid-managed-id&quot;),),
-        ShareeAllowedError: (_CommonObjectResource._storeExceptionError, (calendarserver_namespace, &quot;sharee-privilege-needed&quot;,),),
-        DuplicatePrivateCommentsError: (_CommonObjectResource._storeExceptionError, (calendarserver_namespace, &quot;no-duplicate-private-comments&quot;,),),
-        LockTimeout: (_CommonObjectResource._storeExceptionUnavailable, &quot;Lock timed out.&quot;,),
-        UnknownTimezone: (_CommonObjectResource._storeExceptionError, (caldav_namespace, &quot;valid-timezone&quot;),),
-        AlreadyInTrashError: (_CommonObjectResource._storeExceptionError, (calendarserver_namespace, &quot;not-in-trash&quot;,),),
</del><ins>+        ObjectResourceNameNotAllowedError: (_CommonStoreExceptionHandler._storeExceptionStatus, None,),
+        ObjectResourceNameAlreadyExistsError: (_CommonStoreExceptionHandler._storeExceptionStatus, None,),
+        TooManyObjectResourcesError: (_CommonStoreExceptionHandler._storeExceptionError, customxml.MaxResources(),),
+        ObjectResourceTooBigError: (_CommonStoreExceptionHandler._storeExceptionError, (caldav_namespace, &quot;max-resource-size&quot;),),
+        InvalidObjectResourceError: (_CommonStoreExceptionHandler._storeExceptionError, (caldav_namespace, &quot;valid-calendar-data&quot;),),
+        InvalidComponentForStoreError: (_CommonStoreExceptionHandler._storeExceptionError, (caldav_namespace, &quot;valid-calendar-object-resource&quot;),),
+        InvalidComponentTypeError: (_CommonStoreExceptionHandler._storeExceptionError, (caldav_namespace, &quot;supported-calendar-component&quot;),),
+        TooManyAttendeesError: (_CommonStoreExceptionHandler._storeExceptionError, MaxAttendeesPerInstance.fromString(str(config.MaxAttendeesPerInstance)),),
+        InvalidCalendarAccessError: (_CommonStoreExceptionHandler._storeExceptionError, (calendarserver_namespace, &quot;valid-access-restriction&quot;),),
+        ValidOrganizerError: (_CommonStoreExceptionHandler._storeExceptionError, (calendarserver_namespace, &quot;valid-organizer&quot;),),
+        UIDExistsError: (_CommonStoreExceptionHandler._storeExceptionError, NoUIDConflict(),),
+        UIDExistsElsewhereError: (_CommonStoreExceptionHandler._storeExceptionError, (caldav_namespace, &quot;unique-scheduling-object-resource&quot;),),
+        InvalidUIDError: (_CommonStoreExceptionHandler._storeExceptionError, NoUIDConflict(),),
+        InvalidPerUserDataMerge: (_CommonStoreExceptionHandler._storeExceptionError, (caldav_namespace, &quot;valid-calendar-data&quot;),),
+        AttendeeAllowedError: (_CommonStoreExceptionHandler._storeExceptionError, (caldav_namespace, &quot;attendee-allowed&quot;),),
+        InvalidOverriddenInstanceError: (_CommonStoreExceptionHandler._storeExceptionError, (caldav_namespace, &quot;valid-calendar-data&quot;),),
+        TooManyInstancesError: (_CommonStoreExceptionHandler._storeExceptionError, MaxInstances.fromString(str(config.MaxAllowedInstances)),),
+        AttachmentStoreValidManagedID: (_CommonStoreExceptionHandler._storeExceptionError, (caldav_namespace, &quot;valid-managed-id&quot;),),
+        ShareeAllowedError: (_CommonStoreExceptionHandler._storeExceptionError, (calendarserver_namespace, &quot;sharee-privilege-needed&quot;,),),
+        DuplicatePrivateCommentsError: (_CommonStoreExceptionHandler._storeExceptionError, (calendarserver_namespace, &quot;no-duplicate-private-comments&quot;,),),
+        LockTimeout: (_CommonStoreExceptionHandler._storeExceptionUnavailable, &quot;Lock timed out.&quot;,),
+        UnknownTimezone: (_CommonStoreExceptionHandler._storeExceptionError, (caldav_namespace, &quot;valid-timezone&quot;),),
+        AlreadyInTrashError: (_CommonStoreExceptionHandler._storeExceptionError, (calendarserver_namespace, &quot;not-in-trash&quot;,),),
+        FailedCrossPodRequestError: (_CommonStoreExceptionHandler._storeExceptionUnavailable, &quot;Cross-pod request failed.&quot;,),
</ins><span class="cx">     }
</span><span class="cx"> 
</span><span class="cx">     StoreMoveExceptionsErrors = {
</span><del>-        ObjectResourceNameNotAllowedError: (_CommonObjectResource._storeExceptionStatus, None,),
-        ObjectResourceNameAlreadyExistsError: (_CommonObjectResource._storeExceptionStatus, None,),
-        TooManyObjectResourcesError: (_CommonObjectResource._storeExceptionError, customxml.MaxResources(),),
-        InvalidResourceMove: (_CommonObjectResource._storeExceptionError, (calendarserver_namespace, &quot;valid-move&quot;),),
-        InvalidComponentTypeError: (_CommonObjectResource._storeExceptionError, (caldav_namespace, &quot;supported-calendar-component&quot;),),
-        LockTimeout: (_CommonObjectResource._storeExceptionUnavailable, &quot;Lock timed out.&quot;,),
</del><ins>+        ObjectResourceNameNotAllowedError: (_CommonStoreExceptionHandler._storeExceptionStatus, None,),
+        ObjectResourceNameAlreadyExistsError: (_CommonStoreExceptionHandler._storeExceptionStatus, None,),
+        TooManyObjectResourcesError: (_CommonStoreExceptionHandler._storeExceptionError, customxml.MaxResources(),),
+        InvalidResourceMove: (_CommonStoreExceptionHandler._storeExceptionError, (calendarserver_namespace, &quot;valid-move&quot;),),
+        InvalidComponentTypeError: (_CommonStoreExceptionHandler._storeExceptionError, (caldav_namespace, &quot;supported-calendar-component&quot;),),
+        LockTimeout: (_CommonStoreExceptionHandler._storeExceptionUnavailable, &quot;Lock timed out.&quot;,),
</ins><span class="cx">     }
</span><span class="cx"> 
</span><del>-    StoreRemoveExceptionsErrors = {
-        LockTimeout: (_CommonObjectResource._storeExceptionUnavailable, &quot;Lock timed out.&quot;,),
</del><ins>+    StoreAttachmentValidErrors = {
+        AttachmentStoreFailed: _CommonStoreExceptionHandler._storeExceptionError,
+        InvalidAttachmentOperation: _CommonStoreExceptionHandler._storeExceptionError,
</ins><span class="cx">     }
</span><span class="cx"> 
</span><del>-    StoreAttachmentValidErrors = set((
-        AttachmentStoreFailed,
-        InvalidAttachmentOperation,
-    ))
-
</del><span class="cx">     StoreAttachmentExceptionsErrors = {
</span><del>-        AttachmentStoreValidManagedID: (_CommonObjectResource._storeExceptionError, (caldav_namespace, &quot;valid-managed-id-parameter&quot;,),),
-        AttachmentRemoveFailed: (_CommonObjectResource._storeExceptionError, (caldav_namespace, &quot;valid-attachment-remove&quot;,),),
</del><ins>+        AttachmentStoreValidManagedID: (_CommonStoreExceptionHandler._storeExceptionError, (caldav_namespace, &quot;valid-managed-id-parameter&quot;,),),
+        AttachmentRemoveFailed: (_CommonStoreExceptionHandler._storeExceptionError, (caldav_namespace, &quot;valid-attachment-remove&quot;,),),
</ins><span class="cx">     }
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="lines">@@ -3218,20 +3259,11 @@
</span><span class="cx">         # Map store exception to HTTP errors
</span><span class="cx">         except Exception as err:
</span><span class="cx"> 
</span><del>-            if type(err) in self.StoreAttachmentValidErrors:
-                self._storeExceptionError(err, (caldav_namespace, valid_preconditions[action],))
</del><ins>+            self._handleStoreExceptionArg(err, self.StoreAttachmentValidErrors, (caldav_namespace, valid_preconditions[action],))
+            self._handleStoreException(err, self.StoreAttachmentExceptionsErrors)
+            self._handleStoreException(err, self.StoreExceptionsErrors)
+            raise
</ins><span class="cx"> 
</span><del>-            elif type(err) in self.StoreAttachmentExceptionsErrors:
-                error, arg = self.StoreAttachmentExceptionsErrors[type(err)]
-                error(err, arg)
-
-            elif type(err) in self.StoreExceptionsErrors:
-                error, arg = self.StoreExceptionsErrors[type(err)]
-                error(err, arg)
-
-            else:
-                raise
-
</del><span class="cx">         # Look for Prefer header
</span><span class="cx">         result = yield self._processPrefer(request, post_result)
</span><span class="cx"> 
</span><span class="lines">@@ -3530,31 +3562,28 @@
</span><span class="cx">     vCard = _CommonObjectResource.component
</span><span class="cx"> 
</span><span class="cx">     StoreExceptionsErrors = {
</span><del>-        ObjectResourceNameNotAllowedError: (_CommonObjectResource._storeExceptionStatus, None,),
-        ObjectResourceNameAlreadyExistsError: (_CommonObjectResource._storeExceptionStatus, None,),
-        TooManyObjectResourcesError: (_CommonObjectResource._storeExceptionError, customxml.MaxResources(),),
-        ObjectResourceTooBigError: (_CommonObjectResource._storeExceptionError, (carddav_namespace, &quot;max-resource-size&quot;),),
-        InvalidObjectResourceError: (_CommonObjectResource._storeExceptionError, (carddav_namespace, &quot;valid-address-data&quot;),),
-        InvalidComponentForStoreError: (_CommonObjectResource._storeExceptionError, (carddav_namespace, &quot;valid-addressbook-object-resource&quot;),),
-        UIDExistsError: (_CommonObjectResource._storeExceptionError, NovCardUIDConflict(),),
-        InvalidUIDError: (_CommonObjectResource._storeExceptionError, NovCardUIDConflict(),),
-        InvalidPerUserDataMerge: (_CommonObjectResource._storeExceptionError, (carddav_namespace, &quot;valid-address-data&quot;),),
-        LockTimeout: (_CommonObjectResource._storeExceptionUnavailable, &quot;Lock timed out.&quot;,),
</del><ins>+        ObjectResourceNameNotAllowedError: (_CommonStoreExceptionHandler._storeExceptionStatus, None,),
+        ObjectResourceNameAlreadyExistsError: (_CommonStoreExceptionHandler._storeExceptionStatus, None,),
+        TooManyObjectResourcesError: (_CommonStoreExceptionHandler._storeExceptionError, customxml.MaxResources(),),
+        ObjectResourceTooBigError: (_CommonStoreExceptionHandler._storeExceptionError, (carddav_namespace, &quot;max-resource-size&quot;),),
+        InvalidObjectResourceError: (_CommonStoreExceptionHandler._storeExceptionError, (carddav_namespace, &quot;valid-address-data&quot;),),
+        InvalidComponentForStoreError: (_CommonStoreExceptionHandler._storeExceptionError, (carddav_namespace, &quot;valid-addressbook-object-resource&quot;),),
+        UIDExistsError: (_CommonStoreExceptionHandler._storeExceptionError, NovCardUIDConflict(),),
+        InvalidUIDError: (_CommonStoreExceptionHandler._storeExceptionError, NovCardUIDConflict(),),
+        InvalidPerUserDataMerge: (_CommonStoreExceptionHandler._storeExceptionError, (carddav_namespace, &quot;valid-address-data&quot;),),
+        LockTimeout: (_CommonStoreExceptionHandler._storeExceptionUnavailable, &quot;Lock timed out.&quot;,),
+        FailedCrossPodRequestError: (_CommonStoreExceptionHandler._storeExceptionUnavailable, &quot;Cross-pod request failed.&quot;,),
</ins><span class="cx">     }
</span><span class="cx"> 
</span><span class="cx">     StoreMoveExceptionsErrors = {
</span><del>-        ObjectResourceNameNotAllowedError: (_CommonObjectResource._storeExceptionStatus, None,),
-        ObjectResourceNameAlreadyExistsError: (_CommonObjectResource._storeExceptionStatus, None,),
-        TooManyObjectResourcesError: (_CommonObjectResource._storeExceptionError, customxml.MaxResources(),),
-        InvalidResourceMove: (_CommonObjectResource._storeExceptionError, (calendarserver_namespace, &quot;valid-move&quot;),),
-        LockTimeout: (_CommonObjectResource._storeExceptionUnavailable, &quot;Lock timed out.&quot;,),
</del><ins>+        ObjectResourceNameNotAllowedError: (_CommonStoreExceptionHandler._storeExceptionStatus, None,),
+        ObjectResourceNameAlreadyExistsError: (_CommonStoreExceptionHandler._storeExceptionStatus, None,),
+        TooManyObjectResourcesError: (_CommonStoreExceptionHandler._storeExceptionError, customxml.MaxResources(),),
+        InvalidResourceMove: (_CommonStoreExceptionHandler._storeExceptionError, (calendarserver_namespace, &quot;valid-move&quot;),),
+        LockTimeout: (_CommonStoreExceptionHandler._storeExceptionUnavailable, &quot;Lock timed out.&quot;,),
</ins><span class="cx">     }
</span><span class="cx"> 
</span><del>-    StoreRemoveExceptionsErrors = {
-        LockTimeout: (_CommonObjectResource._storeExceptionUnavailable, &quot;Lock timed out.&quot;,),
-    }
</del><span class="cx"> 
</span><del>-
</del><span class="cx">     def resourceType(self):
</span><span class="cx">         if self.isShared():
</span><span class="cx">             return customxml.ResourceType.sharedownergroup
</span></span></pre></div>
<a id="CalendarServertrunktxdavcaldavdatastoretesttest_sql_externalpy"></a>
<div class="modfile"><h4>Modified: CalendarServer/trunk/txdav/caldav/datastore/test/test_sql_external.py (15538 => 15539)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/trunk/txdav/caldav/datastore/test/test_sql_external.py        2016-04-21 19:48:22 UTC (rev 15538)
+++ CalendarServer/trunk/txdav/caldav/datastore/test/test_sql_external.py        2016-04-21 20:11:28 UTC (rev 15539)
</span><span class="lines">@@ -20,8 +20,11 @@
</span><span class="cx"> from twext.python.clsprop import classproperty
</span><span class="cx"> from txdav.common.datastore.test.util import populateCalendarsFrom
</span><span class="cx"> from txdav.common.datastore.sql_tables import _BIND_MODE_READ, \
</span><del>-    _BIND_STATUS_INVITED, _BIND_MODE_DIRECT, _BIND_STATUS_ACCEPTED
</del><ins>+    _BIND_STATUS_INVITED, _BIND_MODE_DIRECT, _BIND_STATUS_ACCEPTED, \
+    _HOME_STATUS_EXTERNAL, _BIND_MODE_WRITE
</ins><span class="cx"> from txdav.common.datastore.podding.test.util import MultiStoreConduitTest
</span><ins>+from txdav.common.datastore.podding.base import FailedCrossPodRequestError
+from txdav.common.icommondatastore import ExternalShareFailed
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx"> class BaseSharingTests(MultiStoreConduitTest):
</span><span class="lines">@@ -85,6 +88,14 @@
</span><span class="cx"> class CalendarSharing(BaseSharingTests):
</span><span class="cx"> 
</span><span class="cx">     @inlineCallbacks
</span><ins>+    def setUp(self):
+        yield super(CalendarSharing, self).setUp()
+        for store in self.theStores:
+            store._poddingFailure = None
+            store._poddingError = None
+
+
+    @inlineCallbacks
</ins><span class="cx">     def test_no_shares(self):
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         Test that initially there are no shares.
</span><span class="lines">@@ -567,7 +578,167 @@
</span><span class="cx">         yield self.commitTransaction(1)
</span><span class="cx"> 
</span><span class="cx"> 
</span><ins>+    @inlineCallbacks
+    def test_invite_sharee_failure(self):
+        &quot;&quot;&quot;
+        Test invite fails gracefully when the other pod is down.
+        &quot;&quot;&quot;
</ins><span class="cx"> 
</span><ins>+        # Force store to generate 500 error
+        self.patch(self.theStores[1], &quot;_poddingFailure&quot;, ValueError)
+
+        # Invite
+        calendar = yield self.calendarUnderTest(txn=self.theTransactionUnderTest(0), home=&quot;user01&quot;, name=&quot;calendar&quot;)
+        invites = yield calendar.sharingInvites()
+        self.assertEqual(len(invites), 0)
+        self.assertFalse(calendar.isShared())
+
+        yield self.assertFailure(calendar.inviteUIDToShare(&quot;puser02&quot;, _BIND_MODE_READ, &quot;summary&quot;), FailedCrossPodRequestError)
+
+
+    @inlineCallbacks
+    def test_uninvite_sharee_failure(self):
+        &quot;&quot;&quot;
+        Test uninvite fails gracefully when the other pod is down.
+        Also test that the sharee bind entry is removed when an invalid share is detected.
+        &quot;&quot;&quot;
+
+        # Invite
+        sharedName = yield self.createShare(&quot;user01&quot;, &quot;puser02&quot;, &quot;calendar&quot;)
+
+        # Has external sharee bind entry
+        home = yield self.homeUnderTest(
+            txn=self.theTransactionUnderTest(0), name=&quot;puser02&quot;, status=_HOME_STATUS_EXTERNAL
+        )
+        calendar = yield home.anyObjectWithShareUID(sharedName)
+        self.assertTrue(calendar is not None)
+        yield self.commitTransaction(0)
+
+        # Force store to generate 500 error
+        self.patch(self.theStores[1], &quot;_poddingFailure&quot;, ValueError)
+
+        yield self.removeShare(&quot;user01&quot;, &quot;puser02&quot;, &quot;calendar&quot;)
+
+        # Store working again
+        self.patch(self.theStores[1], &quot;_poddingFailure&quot;, None)
+
+        # No external sharee bind entry
+        home = yield self.homeUnderTest(
+            txn=self.theTransactionUnderTest(0), name=&quot;puser02&quot;, status=_HOME_STATUS_EXTERNAL
+        )
+        calendar = yield home.anyObjectWithShareUID(sharedName)
+        self.assertTrue(calendar is None)
+        yield self.commitTransaction(0)
+
+        # Has external sharer bind entry
+        home = yield self.homeUnderTest(
+            txn=self.theTransactionUnderTest(1), name=&quot;user01&quot;, status=_HOME_STATUS_EXTERNAL
+        )
+        calendar = yield home.anyObjectWithShareUID(&quot;calendar&quot;)
+        self.assertTrue(calendar is not None)
+        yield self.commitTransaction(1)
+
+        # Has sharee bind entry
+        home = yield self.homeUnderTest(
+            txn=self.theTransactionUnderTest(1), name=&quot;puser02&quot;
+        )
+        calendar = yield home.anyObjectWithShareUID(sharedName)
+        self.assertTrue(calendar is not None)
+        yield self.commitTransaction(1)
+
+        # Force clean-up of sharee calendar
+        home = yield self.homeUnderTest(
+            txn=self.theTransactionUnderTest(1), name=&quot;puser02&quot;
+        )
+        calendar = yield home.anyObjectWithShareUID(sharedName)
+        yield self.assertFailure(calendar.syncTokenRevision(), ExternalShareFailed)
+        yield self.commitTransaction(1)
+
+        # External sharer bind entry gone
+        home = yield self.homeUnderTest(
+            txn=self.theTransactionUnderTest(1), name=&quot;user01&quot;, status=_HOME_STATUS_EXTERNAL
+        )
+        calendar = yield home.anyObjectWithShareUID(&quot;calendar&quot;)
+        self.assertTrue(calendar is None)
+        yield self.commitTransaction(1)
+
+        # Sharee bind entry gone
+        home = yield self.homeUnderTest(
+            txn=self.theTransactionUnderTest(1), name=&quot;puser02&quot;
+        )
+        calendar = yield home.anyObjectWithShareUID(sharedName)
+        self.assertTrue(calendar is None)
+        yield self.commitTransaction(1)
+
+
+    @inlineCallbacks
+    def test_reply_sharee_failure(self):
+        &quot;&quot;&quot;
+        Test sharee reply fails and cleans up when the share is invalid.
+        &quot;&quot;&quot;
+
+        # Invite
+        home = yield self.homeUnderTest(
+            txn=self.theTransactionUnderTest(0), name=&quot;user01&quot;, create=True
+        )
+        calendar = yield home.calendarWithName(&quot;calendar&quot;)
+        yield calendar.inviteUIDToShare(
+            &quot;puser02&quot;, _BIND_MODE_WRITE, &quot;shared&quot;, shareName=&quot;shared-calendar&quot;
+        )
+        yield self.commitTransaction(0)
+
+        # Has external sharee bind entry
+        home = yield self.homeUnderTest(
+            txn=self.theTransactionUnderTest(0), name=&quot;puser02&quot;, status=_HOME_STATUS_EXTERNAL
+        )
+        calendar = yield home.anyObjectWithShareUID(&quot;shared-calendar&quot;)
+        self.assertTrue(calendar is not None)
+        yield self.commitTransaction(0)
+
+        # Has external sharer bind entry
+        home = yield self.homeUnderTest(
+            txn=self.theTransactionUnderTest(1), name=&quot;user01&quot;, status=_HOME_STATUS_EXTERNAL
+        )
+        calendar = yield home.anyObjectWithShareUID(&quot;calendar&quot;)
+        self.assertTrue(calendar is not None)
+        yield self.commitTransaction(1)
+
+        # Has sharee bind entry
+        home = yield self.homeUnderTest(
+            txn=self.theTransactionUnderTest(1), name=&quot;puser02&quot;
+        )
+        calendar = yield home.anyObjectWithShareUID(&quot;shared-calendar&quot;)
+        self.assertTrue(calendar is not None)
+        yield self.commitTransaction(1)
+
+        # Force store to generate an error
+        self.patch(self.theStores[0], &quot;_poddingError&quot;, ExternalShareFailed)
+
+        # ACK: home2 is None
+        home2 = yield self.homeUnderTest(
+            txn=self.theTransactionUnderTest(1), name=&quot;puser02&quot;
+        )
+        yield self.assertFailure(home2.acceptShare(&quot;shared-calendar&quot;), ExternalShareFailed)
+        yield self.commitTransaction(1)
+
+        # External sharer bind entry gone
+        home = yield self.homeUnderTest(
+            txn=self.theTransactionUnderTest(1), name=&quot;user01&quot;, status=_HOME_STATUS_EXTERNAL
+        )
+        calendar = yield home.anyObjectWithShareUID(&quot;calendar&quot;)
+        self.assertTrue(calendar is None)
+        yield self.commitTransaction(1)
+
+        # Sharee bind entry gone
+        home = yield self.homeUnderTest(
+            txn=self.theTransactionUnderTest(1), name=&quot;puser02&quot;
+        )
+        calendar = yield home.anyObjectWithShareUID(&quot;shared-calendar&quot;)
+        self.assertTrue(calendar is None)
+        yield self.commitTransaction(1)
+
+
+
</ins><span class="cx"> class SharingRevisions(BaseSharingTests):
</span><span class="cx">     &quot;&quot;&quot;
</span><span class="cx">     Test store-based sharing and interaction with revision table.
</span></span></pre></div>
<a id="CalendarServertrunktxdavcommondatastorepoddingsharing_invitespy"></a>
<div class="modfile"><h4>Modified: CalendarServer/trunk/txdav/common/datastore/podding/sharing_invites.py (15538 => 15539)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/trunk/txdav/common/datastore/podding/sharing_invites.py        2016-04-21 19:48:22 UTC (rev 15538)
+++ CalendarServer/trunk/txdav/common/datastore/podding/sharing_invites.py        2016-04-21 20:11:28 UTC (rev 15539)
</span><span class="lines">@@ -166,7 +166,7 @@
</span><span class="cx">         # Sharee home on this pod must already exist
</span><span class="cx">         shareeHome = yield txn.homeWithUID(request[&quot;type&quot;], request[&quot;sharee&quot;])
</span><span class="cx">         if shareeHome is None or shareeHome.external():
</span><del>-            FailedCrossPodRequestError(&quot;Invalid sharee UID specified&quot;)
</del><ins>+            raise FailedCrossPodRequestError(&quot;Invalid sharee UID specified&quot;)
</ins><span class="cx"> 
</span><span class="cx">         # Remove a share
</span><span class="cx">         yield shareeHome.processExternalUninvite(
</span><span class="lines">@@ -229,7 +229,7 @@
</span><span class="cx">         # Sharer home on this pod must already exist
</span><span class="cx">         ownerHome = yield txn.homeWithUID(request[&quot;type&quot;], request[&quot;owner&quot;])
</span><span class="cx">         if ownerHome is None or ownerHome.external():
</span><del>-            FailedCrossPodRequestError(&quot;Invalid owner UID specified&quot;)
</del><ins>+            raise FailedCrossPodRequestError(&quot;Invalid owner UID specified&quot;)
</ins><span class="cx"> 
</span><span class="cx">         # Process a reply
</span><span class="cx">         yield ownerHome.processExternalReply(
</span></span></pre></div>
<a id="CalendarServertrunktxdavcommondatastorepoddingtestutilpy"></a>
<div class="modfile"><h4>Modified: CalendarServer/trunk/txdav/common/datastore/podding/test/util.py (15538 => 15539)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/trunk/txdav/common/datastore/podding/test/util.py        2016-04-21 19:48:22 UTC (rev 15538)
+++ CalendarServer/trunk/txdav/common/datastore/podding/test/util.py        2016-04-21 20:11:28 UTC (rev 15539)
</span><span class="lines">@@ -83,11 +83,20 @@
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx"> 
</span><span class="cx">         store = self.storeMap[self.server.details()]
</span><ins>+
+        # Force a failure of the entire request with the supplied exception type
+        if getattr(store, &quot;_poddingFailure&quot;, None) is not None:
+            raise store._poddingFailure(&quot;Failed cross-pod request&quot;)
+
</ins><span class="cx">         j = json.loads(self.data)
</span><span class="cx">         if self.stream is not None:
</span><span class="cx">             j[&quot;stream&quot;] = self.stream
</span><span class="cx">             j[&quot;streamType&quot;] = self.streamType
</span><span class="cx">         try:
</span><ins>+            # Force a BAD cross-pod request with the supplied exception type
+            if getattr(store, &quot;_poddingError&quot;, None) is not None:
+                raise store._poddingError(&quot;Failed cross-pod request&quot;)
+
</ins><span class="cx">             if store.conduit.isStreamAction(j):
</span><span class="cx">                 stream = ProducerStream()
</span><span class="cx"> 
</span></span></pre></div>
<a id="CalendarServertrunktxdavcommondatastorepoddingutilpy"></a>
<div class="modfile"><h4>Modified: CalendarServer/trunk/txdav/common/datastore/podding/util.py (15538 => 15539)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/trunk/txdav/common/datastore/podding/util.py        2016-04-21 19:48:22 UTC (rev 15538)
+++ CalendarServer/trunk/txdav/common/datastore/podding/util.py        2016-04-21 20:11:28 UTC (rev 15539)
</span><span class="lines">@@ -19,6 +19,7 @@
</span><span class="cx"> from txdav.common.datastore.podding.base import FailedCrossPodRequestError
</span><span class="cx"> from txdav.common.datastore.sql_notification import NotificationCollection, \
</span><span class="cx">     NotificationObject
</span><ins>+from txdav.common.icommondatastore import NonExistentExternalShare
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx"> class UtilityConduitMixin(object):
</span><span class="lines">@@ -137,7 +138,9 @@
</span><span class="cx">         elif &quot;homeChildSharedID&quot; in request:
</span><span class="cx">             homeChild = yield home.childWithName(request[&quot;homeChildSharedID&quot;])
</span><span class="cx">             if homeChild is None:
</span><del>-                raise FailedCrossPodRequestError(&quot;Invalid home child specified&quot;)
</del><ins>+                # Raise NonExistentExternalShare here so we can indicate to the other pod
+                # that it has a bogus share and it can fix itself
+                raise NonExistentExternalShare(&quot;Invalid home child specified&quot;)
</ins><span class="cx">             returnObject = homeChild
</span><span class="cx">             if request.get(&quot;classMethod&quot;, False):
</span><span class="cx">                 classObject = homeChild._objectResourceClass
</span></span></pre></div>
<a id="CalendarServertrunktxdavcommondatastoresql_externalpy"></a>
<div class="modfile"><h4>Modified: CalendarServer/trunk/txdav/common/datastore/sql_external.py (15538 => 15539)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/trunk/txdav/common/datastore/sql_external.py        2016-04-21 19:48:22 UTC (rev 15538)
+++ CalendarServer/trunk/txdav/common/datastore/sql_external.py        2016-04-21 20:11:28 UTC (rev 15539)
</span><span class="lines">@@ -283,6 +283,7 @@
</span><span class="cx">         return True
</span><span class="cx"> 
</span><span class="cx"> 
</span><ins>+    @inlineCallbacks
</ins><span class="cx">     def fixNonExistentExternalShare(self):
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         An external request has returned and indicates the external share no longer exists. That
</span><span class="lines">@@ -291,6 +292,7 @@
</span><span class="cx">         log.error(&quot;Non-existent share detected and removed for {share}&quot;, share=self)
</span><span class="cx">         ownerView = yield self.ownerView()
</span><span class="cx">         yield ownerView.removeShare(self)
</span><ins>+        yield ownerView.cleanExternalShare()
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     @inlineCallbacks
</span></span></pre></div>
<a id="CalendarServertrunktxdavcommondatastoresql_sharingpy"></a>
<div class="modfile"><h4>Modified: CalendarServer/trunk/txdav/common/datastore/sql_sharing.py (15538 => 15539)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/trunk/txdav/common/datastore/sql_sharing.py        2016-04-21 19:48:22 UTC (rev 15538)
+++ CalendarServer/trunk/txdav/common/datastore/sql_sharing.py        2016-04-21 20:11:28 UTC (rev 15539)
</span><span class="lines">@@ -132,9 +132,7 @@
</span><span class="cx"> 
</span><span class="cx">         # See if there are any references to the external share. If not,
</span><span class="cx">         # remove it
</span><del>-        invites = yield ownerView.sharingInvites()
-        if len(invites) == 0:
-            yield ownerHome.removeExternalChild(ownerView)
</del><ins>+        yield ownerView.cleanExternalShare()
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     @inlineCallbacks
</span><span class="lines">@@ -476,7 +474,14 @@
</span><span class="cx">         shareeView = yield self.shareeView(shareeUID)
</span><span class="cx">         if shareeView is not None:
</span><span class="cx">             if shareeView.viewerHome().external():
</span><del>-                yield self._sendExternalUninvite(shareeView)
</del><ins>+                try:
+                    yield self._sendExternalUninvite(shareeView)
+                except Exception as e:
+                    # If the cross-pod request fails for some reason, ignore the exception and go ahead
+                    # and remove the share on this pod. It is up to the other pod to &quot;heal&quot; itself
+                    # by  detecting an invalid share when it is running properly again.
+                    log.error(&quot;Could not send sharing uninvite '{userid}': {ex}&quot;, userid=shareeUID, ex=e)
+
</ins><span class="cx">             else:
</span><span class="cx">                 # If current user state is accepted then we send an invite with the new state, otherwise
</span><span class="cx">                 # we cancel any existing invites for the user. Also, if the ownerHome is disabled, we assume
</span><span class="lines">@@ -551,6 +556,20 @@
</span><span class="cx">             yield self.uninviteUIDFromShare(invitation.shareeUID)
</span><span class="cx"> 
</span><span class="cx"> 
</span><ins>+    @inlineCallbacks
+    def cleanExternalShare(self):
+        &quot;&quot;&quot;
+        Called when an external share is removed. This method will remove the reference to
+        the external shared calendar when there are no more sharees on this pod.
+        &quot;&quot;&quot;
+
+        # See if there are any references to the external share. If not,
+        # remove it
+        invites = yield self.sharingInvites()
+        if len(invites) == 0:
+            yield self._home.removeExternalChild(self)
+
+
</ins><span class="cx">     def newShare(self, displayname=None):
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         Override in derived classes to do any specific operations needed when a share
</span><span class="lines">@@ -694,15 +713,21 @@
</span><span class="cx">     @inlineCallbacks
</span><span class="cx">     def _replyExternalInvite(self, status, summary=None):
</span><span class="cx"> 
</span><del>-        yield self._txn.store().conduit.send_sharereply(
-            self._txn,
-            self.viewerHome()._homeType,
-            self.ownerHome().uid(),
-            self.viewerHome().uid(),
-            self.shareUID(),
-            status,
-            summary,
-        )
</del><ins>+        # If a reply to an external share fails, then assume the external share
+        # is broken and remove it from the local pod
+        try:
+            yield self._txn.store().conduit.send_sharereply(
+                self._txn,
+                self.viewerHome()._homeType,
+                self.ownerHome().uid(),
+                self.viewerHome().uid(),
+                self.shareUID(),
+                status,
+                summary,
+            )
+        except ExternalShareFailed:
+            yield self.fixNonExistentExternalShare()
+            raise ExternalShareFailed(&quot;External share does not exist&quot;)
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     #
</span></span></pre>
</div>
</div>

</body>
</html>