[CalendarServer-changes] [5917] CalendarServer/branches/new-store/twistedcaldav

source_changes at macosforge.org source_changes at macosforge.org
Mon Jul 19 15:51:06 PDT 2010


Revision: 5917
          http://trac.macosforge.org/projects/calendarserver/changeset/5917
Author:   glyph at apple.com
Date:     2010-07-19 15:51:06 -0700 (Mon, 19 Jul 2010)
Log Message:
-----------
add and test a response filter that will roll back any uncommitted transactions by the time the response is being relayed to the client

Modified Paths:
--------------
    CalendarServer/branches/new-store/twistedcaldav/static.py
    CalendarServer/branches/new-store/twistedcaldav/test/test_wrapping.py

Modified: CalendarServer/branches/new-store/twistedcaldav/static.py
===================================================================
--- CalendarServer/branches/new-store/twistedcaldav/static.py	2010-07-19 22:46:06 UTC (rev 5916)
+++ CalendarServer/branches/new-store/twistedcaldav/static.py	2010-07-19 22:51:06 UTC (rev 5917)
@@ -66,6 +66,7 @@
 from twext.web2.dav.resource import davPrivilegeSet
 from twext.web2.dav.util import parentForURL, bindMethods, joinURL
 from twext.web2.http_headers import generateContentType, MimeType
+from txdav.idav import AlreadyFinishedError
 
 from twistedcaldav import caldavxml
 from twistedcaldav import carddavxml
@@ -890,6 +891,44 @@
 
 
 
+def _transactionFromRequest(request, newStore):
+    """
+    Return the associated transaction from the given HTTP request, creating a
+    new one from the given data store if none has yet been associated.
+
+    Also, if the request was not previously associated with a transaction, add
+    a failsafe transaction-abort response filter to abort any transaction which
+    has not been committed or aborted by the resource which responds to the
+    request.
+
+    @param request: The request to inspect.
+    @type request: L{IRequest}
+
+    @param newStore: The store to create a transaction from.
+    @type newStore: L{IDataStore}
+
+    @return: a transaction that should be used to read and write data
+        associated with the request.
+    @rtype: L{ITransaction} (and possibly L{ICalendarTransaction} and
+        L{IAddressBookTransaction} as well.
+    """
+    TRANSACTION_KEY = '_newStoreTransaction'
+    transaction = getattr(request, TRANSACTION_KEY, None)
+    if transaction is None:
+        transaction = newStore.newTransaction(repr(request))
+        def abortIfUncommitted(request, response):
+            try:
+                transaction.abort()
+            except AlreadyFinishedError:
+                pass
+            return response
+        abortIfUncommitted.handleErrors = True
+        request.addResponseFilter(abortIfUncommitted)
+        setattr(request, TRANSACTION_KEY, transaction)
+    return transaction
+
+
+
 class CalendarHomeUIDProvisioningFile (AutoProvisioningFileMixIn, DirectoryCalendarHomeUIDProvisioningResource, DAVFile):
     def __init__(self, path, parent, homeResourceClass=None):
         """
@@ -917,11 +956,7 @@
 
     def homeResourceForRecord(self, record, request):
         self.provision()
-        TRANSACTION_KEY = '_newStoreTransaction'
-        transaction = getattr(request, TRANSACTION_KEY, None)
-        if transaction is None:
-            transaction = self.parent._newStore.newTransaction(repr(request))
-            setattr(request, TRANSACTION_KEY, transaction)
+        transaction = _transactionFromRequest(request, self.parent._newStore)
 
         name = record.uid
 
@@ -1599,11 +1634,7 @@
 
     def homeResourceForRecord(self, record, request):
         self.provision()
-        TRANSACTION_KEY = '_newStoreTransaction'
-        transaction = getattr(request, TRANSACTION_KEY, None)
-        if transaction is None:
-            transaction = self.parent._newStore.newTransaction(repr(request))
-            setattr(request, TRANSACTION_KEY, transaction)
+        transaction = _transactionFromRequest(request, self.parent._newStore)
 
         name = record.uid
 

Modified: CalendarServer/branches/new-store/twistedcaldav/test/test_wrapping.py
===================================================================
--- CalendarServer/branches/new-store/twistedcaldav/test/test_wrapping.py	2010-07-19 22:46:06 UTC (rev 5916)
+++ CalendarServer/branches/new-store/twistedcaldav/test/test_wrapping.py	2010-07-19 22:51:06 UTC (rev 5917)
@@ -18,8 +18,12 @@
 Tests for the interaction between model-level and protocol-level logic.
 """
 
+from twext.web2.server import Request
+from twext.web2.responsecode import OK, UNAUTHORIZED
+from twext.web2.http_headers import Headers
+from txdav.idav import AlreadyFinishedError
+
 from twext.web2.dav import davxml
-from twext.web2.dav.element.base import dav_namespace
 from twistedcaldav.config import config
 
 from twisted.internet.defer import inlineCallbacks, returnValue
@@ -39,6 +43,28 @@
 from txcarddav.addressbookstore.test.test_file import vcard4_text
 
 
+class FakeChanRequest(object):
+    def writeHeaders(self, code, headers):
+        self.code = code
+        self.headers = headers
+    def registerProducer(self, producer, streaming):
+        pass
+    def write(self, data):
+        pass
+    def unregisterProducer(self):
+        pass
+    def abortConnection(self):
+        pass
+    def getHostInfo(self):
+        return '127.0.0.1', False
+    def getRemoteHost(self):
+        return '127.0.0.1'
+    def finish(self):
+        pass
+
+
+
+
 class WrappingTests(TestCase):
     """
     Tests for L{twistedcaldav.static.CalDAVFile} creating the appropriate type
@@ -65,7 +91,7 @@
         @param objectName: The name of a calendar object.
         @type objectName: str
         @param objectText: Some iCalendar text to populate it with.
-        @type objectText: str 
+        @type objectText: str
         """
         record = self.directoryService.recordWithShortName("users", "wsanchez")
         uid = record.uid
@@ -89,7 +115,7 @@
         @param objectName: The name of a addressbook object.
         @type objectName: str
         @param objectText: Some iVcard text to populate it with.
-        @type objectText: str 
+        @type objectText: str
         """
         record = self.directoryService.recordWithShortName("users", "wsanchez")
         uid = record.uid
@@ -109,6 +135,8 @@
         txn.commit()
 
 
+    requestUnderTest = None
+
     @inlineCallbacks
     def getResource(self, path):
         """
@@ -119,11 +147,15 @@
 
         @type path: C{str}
         """
-        segments = path.split("/")
-        resource = self.site.resource
-        while segments:
-            resource, segments = yield resource.locateChild(self, segments)
-        returnValue(resource)
+        if self.requestUnderTest is None:
+            req = self.requestForPath(path)
+            self.requestUnderTest = req
+        else:
+            req = self.requestUnderTest
+        aResource = yield req.locateResource(
+            "http://localhost:8008/" + path
+        )
+        returnValue(aResource)
 
 
     def commit(self):
@@ -132,9 +164,59 @@
         an associated transaction.  Commit that transaction to bring the
         filesystem into a consistent state.
         """
-        self._newStoreTransaction.commit()
+        self.requestUnderTest._newStoreTransaction.commit()
 
 
+    def requestForPath(self, path):
+        """
+        Get a L{Request} with a L{FakeChanRequest} for a given path.
+        """
+        headers = Headers()
+        headers.addRawHeader("Host", "localhost:8008")
+        chanReq = FakeChanRequest()
+        req = Request(
+            site=self.site,
+            chanRequest=chanReq,
+            command='GET',
+            path=path,
+            version=('1', '1'),
+            contentLength=0,
+            headers=headers
+        )
+        req.credentialFactories = {}
+        return req
+
+
+    @inlineCallbacks
+    def test_autoRevertUnCommitted(self):
+        """
+        Resources that need to read from the back-end in a transaction will be
+        reverted by a response filter in the case where the request does not
+        commit them.  This can happen, for example, with resources that are
+        children of non-existent (proto-)resources.
+        """
+        for pathType in ['calendar', 'addressbook']:
+            req = self.requestForPath('/%ss/users/wsanchez/%s/forget/it'
+                                      % (pathType, pathType))
+            yield req.process()
+            self.assertEquals(req.chanRequest.code, 404)
+            self.assertRaises(AlreadyFinishedError,
+                              req._newStoreTransaction.commit)
+
+
+    @inlineCallbacks
+    def test_simpleRequest(self):
+        """
+        Sanity check and integration test: an unauthorized request of calendar
+        and addressbook resources results in an L{UNAUTHORIZED} response code.
+        """
+        for pathType in ['calendar', 'addressbook']:
+            req = self.requestForPath('/%ss/users/wsanchez/%s/'
+                                      % (pathType, pathType))
+            yield req.process()
+            self.assertEquals(req.chanRequest.code, UNAUTHORIZED)
+
+
     def test_createStore(self):
         """
         Creating a CalendarHomeProvisioningFile will create a paired
@@ -221,6 +303,7 @@
             self.assertIdentical(
                 getattr(calDavFile, "_newStoreCalendar", None), None
             )
+        self.commit()
 
 
     @inlineCallbacks
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.macosforge.org/pipermail/calendarserver-changes/attachments/20100719/49ec87af/attachment.html>


More information about the calendarserver-changes mailing list