[CalendarServer-changes] [7851] CalendarServer/trunk

source_changes at macosforge.org source_changes at macosforge.org
Mon Aug 1 23:15:45 PDT 2011


Revision: 7851
          http://trac.macosforge.org/projects/calendarserver/changeset/7851
Author:   sagen at apple.com
Date:     2011-08-01 23:15:44 -0700 (Mon, 01 Aug 2011)
Log Message:
-----------
Adds a lock for GroupCacher updates; cleans up some memcacher inconsistencies and adds checkAndSet support; reduces number of messages logged for missing guid attr in LDAP

Modified Paths:
--------------
    CalendarServer/trunk/twistedcaldav/directory/digest.py
    CalendarServer/trunk/twistedcaldav/directory/directory.py
    CalendarServer/trunk/twistedcaldav/directory/ldapdirectory.py
    CalendarServer/trunk/twistedcaldav/directory/test/test_directory.py
    CalendarServer/trunk/twistedcaldav/directory/test/test_ldapdirectory.py
    CalendarServer/trunk/twistedcaldav/memcachelock.py
    CalendarServer/trunk/twistedcaldav/memcachepool.py
    CalendarServer/trunk/twistedcaldav/memcacher.py
    CalendarServer/trunk/twistedcaldav/test/test_memcacher.py
    CalendarServer/trunk/txdav/common/datastore/test/util.py

Modified: CalendarServer/trunk/twistedcaldav/directory/digest.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/directory/digest.py	2011-08-01 23:43:35 UTC (rev 7850)
+++ CalendarServer/trunk/twistedcaldav/directory/digest.py	2011-08-02 06:15:44 UTC (rev 7851)
@@ -115,7 +115,7 @@
         super(DigestCredentialsMemcache, self).set(
             key,
             value,
-            expire_time=self.CHALLENGE_MAXTIME_SECS
+            expireTime=self.CHALLENGE_MAXTIME_SECS
         )
 
 class QopDigestCredentialFactory(DigestCredentialFactory):

Modified: CalendarServer/trunk/twistedcaldav/directory/directory.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/directory/directory.py	2011-08-01 23:43:35 UTC (rev 7850)
+++ CalendarServer/trunk/twistedcaldav/directory/directory.py	2011-08-02 06:15:44 UTC (rev 7851)
@@ -28,6 +28,7 @@
     "UnknownRecordTypeError",
 ]
 
+import datetime
 import os
 import sys
 import types
@@ -395,13 +396,14 @@
 
     Keys in this cache are:
 
-    "group-membership-cache-populated" : gets set to "true" after the cache
-    is populated, so clients know they can now use it.  Note, this needs to
-    be made robust in the face of memcached evictions.
-
     "groups-for:<GUID>" : comma-separated list of groups that GUID is a member
-    of
+    of.  Note that when using LDAP, the key for this is an LDAP DN.
 
+    "group-cacher-populated" : contains a datestamp indicating the most recent
+    population.
+
+    "group-cacher-lock" : used to prevent multiple updates, it has a value of "1"
+
     """
 
     def __init__(self, namespace, pickle=False, no_invalidation=False,
@@ -417,7 +419,7 @@
         self.log_debug("set groups-for %s : %s" % (guid, memberships))
         return self.set("groups-for:%s" %
             (str(guid)), str(",".join(memberships)),
-            expire_time=self.expireSeconds)
+            expireTime=self.expireSeconds)
 
     def getGroupsFor(self, guid):
         self.log_debug("get groups-for %s" % (guid,))
@@ -430,8 +432,21 @@
         d.addCallback(_value)
         return d
 
+    def setPopulatedMarker(self):
+        return self.set("group-cacher-populated", str(datetime.datetime.now()))
 
+    @inlineCallbacks
+    def isPopulated(self):
+        value = (yield self.get("group-cacher-populated"))
+        returnValue(value is not None)
 
+    def acquireLock(self):
+        return self.add("group-cacher-lock", "1", expireTime=self.expireSeconds)
+
+    def releaseLock(self):
+        return self.delete("group-cacher-lock")
+
+
 class GroupMembershipCacheUpdater(LoggingMixIn):
     """
     Responsible for updating memcached with group memberships.  This will run
@@ -494,9 +509,36 @@
         delegated to.  Fault these principals in.  For any of these principals
         that are groups, expand the members of that group and store those in
         the cache
+
+        If fast=True, we're in quick-start mode, used only by the master process
+        to start servicing requests as soon as possible.  In this mode we look
+        for DataRoot/memberships_cache which is a pickle of a dictionary whose
+        keys are guids (except when using LDAP where the keys will be DNs), and
+        the values are lists of group guids.  If the cache file does not exist
+        we switch to fast=False.
+
+        The return value is mainly used for unit tests; it's a tuple containing
+        the (possibly modified) value for fast, and the number of members loaded
+        into the cache (which can be zero if fast=True and isPopulated(), or
+        fast=False and the cache is locked by someone else).
         """
+
         # TODO: add memcached eviction protection
 
+        # See if anyone has completely populated the group membership cache
+        isPopulated = (yield self.cache.isPopulated())
+
+        useLock = True
+
+        if fast:
+            # We're in quick-start mode.  Check first to see if someone has
+            # populated the membership cache, and if so, return immediately
+            if isPopulated:
+                returnValue((fast, 0))
+
+            # We don't care what others are doing right now, we need to update
+            useLock = False
+
         self.log_debug("Updating group membership cache")
 
         dataRoot = FilePath(config.DataRoot)
@@ -509,6 +551,14 @@
             self.log_debug("Group membership snapshot file exists: %s" %
                            (snapshotFile.path,))
 
+        if useLock:
+            self.log_debug("Attempting to acquire group membership cache lock")
+            acquiredLock = (yield self.cache.acquireLock())
+            if not acquiredLock:
+                self.log_debug("Group membership cache lock held by another process")
+                returnValue((fast, 0))
+            self.log_debug("Acquired lock")
+
         if not fast and self.useExternalProxies:
             self.log_debug("Retrieving proxy assignments from directory")
             assignments = self.externalProxiesSource()
@@ -581,6 +631,12 @@
             # self.log_debug("%s is in %s" % (member, groups))
             yield self.cache.setGroupsFor(member, groups)
 
+        yield self.cache.setPopulatedMarker()
+
+        if useLock:
+            self.log_debug("Releasing lock")
+            yield self.cache.releaseLock()
+
         self.log_debug("Group memberships cache updated")
 
         returnValue((fast, len(members)))

Modified: CalendarServer/trunk/twistedcaldav/directory/ldapdirectory.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/directory/ldapdirectory.py	2011-08-01 23:43:35 UTC (rev 7850)
+++ CalendarServer/trunk/twistedcaldav/directory/ldapdirectory.py	2011-08-02 06:15:44 UTC (rev 7851)
@@ -296,20 +296,22 @@
             ldap.SCOPE_SUBTREE, filter, self.attrList)
 
         records = []
-
+        numMissingGuids = 0
+        guidAttr = self.rdnSchema["guidAttr"]
         for dn, attrs in results:
 
             unrestricted = True
             if self.restrictedGUIDs is not None:
-                guidAttr = self.rdnSchema["guidAttr"]
                 if guidAttr:
                     guid = self._getUniqueLdapAttribute(attrs, guidAttr)
                     if guid not in self.restrictedGUIDs:
                         unrestricted = False
 
-            record = self._ldapResultToRecord(dn, attrs, recordType)
-            # self.log_debug("Got LDAP record %s" % (record,))
-            if record is None:
+            try:
+                record = self._ldapResultToRecord(dn, attrs, recordType)
+                # self.log_debug("Got LDAP record %s" % (record,))
+            except MissingGuidException:
+                numMissingGuids += 1
                 continue
 
             if not unrestricted:
@@ -319,6 +321,10 @@
 
             records.append(record)
 
+        if numMissingGuids:
+            self.log_warn("%d %s records are missing %s" %
+                (numMissingGuids, recordType, guidAttr))
+
         return records
 
 
@@ -517,8 +523,8 @@
         Convert the attrs returned by a LDAP search into a LdapDirectoryRecord
         object.
 
-        Mappings are hardcoded below but the most standard LDAP schemas were
-        used to define them
+        If guidAttr was specified in the config but is missing from attrs,
+        raises MissingGuidException
         """
 
         guid = None
@@ -538,15 +544,12 @@
         if guidAttr:
             guid = self._getUniqueLdapAttribute(attrs, guidAttr)
             if not guid:
-                self.log_error("LDAP data missing required GUID attribute: %s" %
-                    (guidAttr,))
-                return None
+                raise MissingGuidException()
 
         # Find or build email
         emailAddresses = self._getMultipleLdapAttributes(attrs, self.rdnSchema[recordType]["mapping"]["emailAddresses"])
         emailSuffix = self.rdnSchema[recordType]["emailSuffix"]
 
-
         if len(emailAddresses) == 0 and emailSuffix:
             emailPrefix = self._getUniqueLdapAttribute(attrs,
                 self.rdnSchema[recordType]["attr"])
@@ -709,6 +712,7 @@
         self.log_debug("LDAP query for types %s, indexType %s and indexKey %s"
             % (recordTypes, indexType, indexKey))
 
+        guidAttr = self.rdnSchema["guidAttr"]
         for recordType in recordTypes:
             # Build base for this record Type
             base = self.typeRDNs[recordType] + self.base
@@ -723,7 +727,6 @@
                 # Query on guid only works if guid attribute has been defined.
                 # Support for query on guid even if is auto-generated should
                 # be added.
-                guidAttr = self.rdnSchema["guidAttr"]
                 if not guidAttr: return
                 filter = "(&%s(%s=%s))" % (filter, guidAttr, indexKey)
 
@@ -762,16 +765,15 @@
 
                 unrestricted = True
                 if self.restrictedGUIDs is not None:
-                    guidAttr = self.rdnSchema["guidAttr"]
                     if guidAttr:
                         guid = self._getUniqueLdapAttribute(attrs, guidAttr)
                         if guid not in self.restrictedGUIDs:
                             unrestricted = False
 
-                record = self._ldapResultToRecord(dn, attrs, recordType)
-                self.log_debug("Got LDAP record %s" % (record,))
+                try:
+                    record = self._ldapResultToRecord(dn, attrs, recordType)
+                    self.log_debug("Got LDAP record %s" % (record,))
 
-                if record is not None:
                     self.recordCacheForType(recordType).addRecord(record,
                         indexType, indexKey
                     )
@@ -783,6 +785,10 @@
 
                     record.applySACLs()
 
+                except MissingGuidException:
+                    self.log_warn("LDAP data missing required GUID attribute: %s" %
+                        (guidAttr,))
+
     def recordsMatchingFields(self, fields, operand="or", recordType=None):
         """
         Carries out the work of a principal-property-search against LDAP
@@ -792,11 +798,12 @@
 
         self.log_debug("Peforming principal property search for %s" % (fields,))
         recordTypes = [recordType] if recordType else self.recordTypes()
+        guidAttr = self.rdnSchema["guidAttr"]
         for recordType in recordTypes:
-            filter = buildFilter(self.rdnSchema[recordType]["mapping"], fields, operand=operand)
+            filter = buildFilter(self.rdnSchema[recordType]["mapping"], fields,
+                operand=operand)
 
             if filter is not None:
-
                 # Query the LDAP server
                 base = self.typeRDNs[recordType] + self.base
 
@@ -806,29 +813,35 @@
                     ldap.SCOPE_SUBTREE, filter, self.attrList)
                 self.log_debug("LDAP search returned %d results" % (len(results),))
 
+                numMissingGuids = 0
                 for dn, attrs in results:
                     # Skip if group restriction is in place and guid is not
                     # a member
                     if (recordType != self.recordType_groups and
                         self.restrictedGUIDs is not None):
-                        guidAttr = self.rdnSchema["guidAttr"]
                         if guidAttr:
                             guid = self._getUniqueLdapAttribute(attrs, guidAttr)
                             if guid not in self.restrictedGUIDs:
                                 continue
 
-                    record = self._ldapResultToRecord(dn, attrs, recordType)
-                    if record is None:
-                        continue
+                    try:
+                        record = self._ldapResultToRecord(dn, attrs, recordType)
 
-                    # For non-group records, if not enabled for calendaring do
-                    # not include in principal property search results
-                    if (recordType != self.recordType_groups):
-                        if not record.enabledForCalendaring:
-                            continue
+                        # For non-group records, if not enabled for calendaring do
+                        # not include in principal property search results
+                        if (recordType != self.recordType_groups):
+                            if not record.enabledForCalendaring:
+                                continue
 
-                    records.append(record)
+                        records.append(record)
 
+                    except MissingGuidException:
+                        numMissingGuids += 1
+
+                if numMissingGuids:
+                    self.log_warn("%d %s records are missing %s" %
+                        (numMissingGuids, recordType, guidAttr))
+
         self.log_debug("Principal property search matched %d records" % (len(records),))
         return succeed(records)
 
@@ -1095,3 +1108,7 @@
 
         return super(LdapDirectoryRecord, self).verifyCredentials(credentials)
 
+
+class MissingGuidException(Exception):
+    """ Raised when LDAP record is missing guidAttr and it's required """
+    pass

Modified: CalendarServer/trunk/twistedcaldav/directory/test/test_directory.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/directory/test/test_directory.py	2011-08-01 23:43:35 UTC (rev 7850)
+++ CalendarServer/trunk/twistedcaldav/directory/test/test_directory.py	2011-08-02 06:15:44 UTC (rev 7851)
@@ -184,7 +184,7 @@
         Let the GroupMembershipCacheUpdater populate the cache, then make
         sure proxyFor( ) and groupMemberships( ) work from the cache
         """
-        cache = GroupMembershipCache("ProxyDB", 60)
+        cache = GroupMembershipCache("ProxyDB", expireSeconds=60)
         # Having a groupMembershipCache assigned to the directory service is the
         # trigger to use such a cache:
         self.directoryService.groupMembershipCache = cache
@@ -238,8 +238,22 @@
             )
         )
 
-        yield updater.updateCache()
+        # Prevent an update by locking the cache
+        acquiredLock = (yield cache.acquireLock())
+        self.assertTrue(acquiredLock)
+        self.assertEquals((False, 0), (yield updater.updateCache()))
 
+        # You can't lock when already locked:
+        acquiredLockAgain = (yield cache.acquireLock())
+        self.assertFalse(acquiredLockAgain)
+
+        # Allow an update by unlocking the cache
+        yield cache.releaseLock()
+        self.assertEquals((False, 8), (yield updater.updateCache()))
+
+        # Verify cache is populated:
+        self.assertTrue((yield cache.isPopulated()))
+
         delegates = (
 
             # record name
@@ -325,7 +339,7 @@
         Exercise external proxy assignment support (assignments come from the
         directory service itself)
         """
-        cache = GroupMembershipCache("ProxyDB", 60)
+        cache = GroupMembershipCache("ProxyDB", expireSeconds=60)
         # Having a groupMembershipCache assigned to the directory service is the
         # trigger to use such a cache:
         self.directoryService.groupMembershipCache = cache
@@ -426,7 +440,7 @@
         the member -> groups dictionary, and can quickly refresh memcached
         from that snapshot when restarting the server.
         """
-        cache = GroupMembershipCache("ProxyDB", 60)
+        cache = GroupMembershipCache("ProxyDB", expireSeconds=60)
         # Having a groupMembershipCache assigned to the directory service is the
         # trigger to use such a cache:
         self.directoryService.groupMembershipCache = cache
@@ -444,17 +458,28 @@
         # Try a fast update (as when the server starts up for the very first
         # time), but since the snapshot doesn't exist we fault in from the
         # directory (fast now is False), and snapshot will get created
+
+        # Note that because fast=True and isPopulated() is False, locking is
+        # ignored:
+        acquiredLock = (yield cache.acquireLock())
+
+        self.assertFalse((yield cache.isPopulated()))
         fast, numMembers = (yield updater.updateCache(fast=True))
         self.assertEquals(fast, False)
         self.assertEquals(numMembers, 8)
         self.assertTrue(snapshotFile.exists())
+        self.assertTrue((yield cache.isPopulated()))
 
+        yield cache.releaseLock()
+
         # Try another fast update where the snapshot already exists (as in a
         # server-restart scenario), which will only read from the snapshot
-        # as indicated by the return value for "fast"
+        # as indicated by the return value for "fast".  Note that the cache
+        # is already populated so updateCache( ) in fast mode will not do
+        # anything, and numMembers will be 0.
         fast, numMembers = (yield updater.updateCache(fast=True))
         self.assertEquals(fast, True)
-        self.assertEquals(numMembers, 8)
+        self.assertEquals(numMembers, 0)
 
         # Try an update which faults in from the directory (fast=False)
         fast, numMembers = (yield updater.updateCache(fast=False))
@@ -511,3 +536,5 @@
                     ])
             }
         )
+
+

Modified: CalendarServer/trunk/twistedcaldav/directory/test/test_ldapdirectory.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/directory/test/test_ldapdirectory.py	2011-08-01 23:43:35 UTC (rev 7850)
+++ CalendarServer/trunk/twistedcaldav/directory/test/test_ldapdirectory.py	2011-08-02 06:15:44 UTC (rev 7851)
@@ -15,7 +15,9 @@
 ##
 
 try:
-    from twistedcaldav.directory.ldapdirectory import buildFilter, LdapDirectoryService
+    from twistedcaldav.directory.ldapdirectory import (
+        buildFilter, LdapDirectoryService, MissingGuidException
+    )
 except ImportError:
     print "Skipping because ldap module not installed"
 else:
@@ -284,9 +286,9 @@
                 'cn': ['Amanda Test'],
             }
 
-            record = self.service._ldapResultToRecord(dn, attrs,
+            self.assertRaises(MissingGuidException,
+                self.service._ldapResultToRecord, dn, attrs,
                 self.service.recordType_users)
-            self.assertEquals(record, None)
 
             # Group with direct user members and nested group
 

Modified: CalendarServer/trunk/twistedcaldav/memcachelock.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/memcachelock.py	2011-08-01 23:43:35 UTC (rev 7850)
+++ CalendarServer/trunk/twistedcaldav/memcachelock.py	2011-08-02 06:15:44 UTC (rev 7851)
@@ -62,7 +62,7 @@
         waiting = False
         while True:
             
-            result = (yield self.add(self._locktoken, "1", self._expire_time))
+            result = (yield self.add(self._locktoken, "1", expireTime=self._expire_time))
             if result:
                 self._hasLock = True
                 if waiting:

Modified: CalendarServer/trunk/twistedcaldav/memcachepool.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/memcachepool.py	2011-08-01 23:43:35 UTC (rev 7850)
+++ CalendarServer/trunk/twistedcaldav/memcachepool.py	2011-08-02 06:15:44 UTC (rev 7851)
@@ -348,20 +348,20 @@
     def get(self, *args, **kwargs):
         return self.performRequest('get', *args, **kwargs)
 
-
     def set(self, *args, **kwargs):
         return self.performRequest('set', *args, **kwargs)
 
+    def checkAndSet(self, *args, **kwargs):
+        return self.performRequest('checkAndSet', *args, **kwargs)
 
     def delete(self, *args, **kwargs):
         return self.performRequest('delete', *args, **kwargs)
 
-
     def add(self, *args, **kwargs):
         return self.performRequest('add', *args, **kwargs)
 
-    def flush_all(self, *args, **kwargs):
-        return self.performRequest('flush_all', *args, **kwargs)
+    def flushAll(self, *args, **kwargs):
+        return self.performRequest('flushAll', *args, **kwargs)
 
 
 

Modified: CalendarServer/trunk/twistedcaldav/memcacher.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/memcacher.py	2011-08-01 23:43:35 UTC (rev 7850)
+++ CalendarServer/trunk/twistedcaldav/memcacher.py	2011-08-02 06:15:44 UTC (rev 7851)
@@ -46,14 +46,14 @@
         """
 
         def __init__(self):
-            self._cache = {}
+            self._cache = {} # (value, expireTime, check-and-set identifier)
             self._clock = 0
 
         def add(self, key, value, expireTime=0):
             if key not in self._cache:
                 if not expireTime:
                     expireTime = 99999
-                self._cache[key] = (value, self._clock + expireTime)
+                self._cache[key] = (value, self._clock + expireTime, 0)
                 return succeed(True)
             else:
                 return succeed(False)
@@ -61,13 +61,34 @@
         def set(self, key, value, expireTime=0):
             if not expireTime:
                 expireTime = 99999
-            self._cache[key] = (value, self._clock + expireTime)
+            if self._cache.has_key(key):
+                identifier = self._cache[key][2]
+                identifier += 1
+            else:
+                identifier = 0
+            self._cache[key] = (value, self._clock + expireTime, identifier)
             return succeed(True)
 
-        def get(self, key):
-            value, expires = self._cache.get(key, (None, 0))
+        def checkAndSet(self, key, value, cas, flags=0, expireTime=0):
+            if not expireTime:
+                expireTime = 99999
+            if self._cache.has_key(key):
+                identifier = self._cache[key][2]
+                if cas != str(identifier):
+                    return succeed(False)
+                identifier += 1
+            else:
+                return succeed(False)
+            self._cache[key] = (value, self._clock + expireTime, identifier)
+            return succeed(True)
+
+        def get(self, key, withIdentifier=False):
+            value, expires, identifier = self._cache.get(key, (None, 0, ""))
             if self._clock >= expires:
-                return succeed((0, None,))
+                value = None
+                identifier = ""
+            if withIdentifier:
+                return succeed((0, value, str(identifier)))
             else:
                 return succeed((0, value,))
 
@@ -78,7 +99,7 @@
             except KeyError:
                 return succeed(False)
 
-        def flush_all(self):
+        def flushAll(self):
             self._cache = {}
             return succeed(True)
 
@@ -102,13 +123,16 @@
         def set(self, key, value, expireTime=0):
             return succeed(True)
 
-        def get(self, key):
+        def checkAndSet(self, key, value, cas, flags=0, expireTime=0):
+            return succeed(True)
+
+        def get(self, key, withIdentifier=False):
             return succeed((0, None,))
 
         def delete(self, key):
             return succeed(True)
 
-        def flush_all(self):
+        def flushAll(self):
             return succeed(True)
 
     def __init__(self, namespace, pickle=False, no_invalidation=False, key_normalization=True):
@@ -170,7 +194,7 @@
         else:
             return key
 
-    def add(self, key, value, expire_time=0):
+    def add(self, key, value, expireTime=0):
         
         proto = self._getMemcacheProtocol()
 
@@ -178,9 +202,9 @@
         if self._pickle:
             my_value = cPickle.dumps(value)
         self.log_debug("Adding Cache Token for %r" % (key,))
-        return proto.add('%s:%s' % (self._namespace, self._normalizeKey(key)), my_value, expireTime=expire_time)
+        return proto.add('%s:%s' % (self._namespace, self._normalizeKey(key)), my_value, expireTime=expireTime)
 
-    def set(self, key, value, expire_time=0):
+    def set(self, key, value, expireTime=0):
         
         proto = self._getMemcacheProtocol()
 
@@ -188,24 +212,39 @@
         if self._pickle:
             my_value = cPickle.dumps(value)
         self.log_debug("Setting Cache Token for %r" % (key,))
-        return proto.set('%s:%s' % (self._namespace, self._normalizeKey(key)), my_value, expireTime=expire_time)
+        return proto.set('%s:%s' % (self._namespace, self._normalizeKey(key)), my_value, expireTime=expireTime)
 
-    def get(self, key):
-        def _gotit(result):
-            _ignore_flags, value = result
+    def checkAndSet(self, key, value, cas, flags=0, expireTime=0):
+
+        proto = self._getMemcacheProtocol()
+
+        my_value = value
+        if self._pickle:
+            my_value = cPickle.dumps(value)
+        self.log_debug("Setting Cache Token for %r" % (key,))
+        return proto.checkAndSet('%s:%s' % (self._namespace, self._normalizeKey(key)), my_value, cas, expireTime=expireTime)
+
+    def get(self, key, withIdentifier=False):
+        def _gotit(result, withIdentifier):
+            if withIdentifier:
+                _ignore_flags, identifier, value = result
+            else:
+                _ignore_flags, value = result
             if self._pickle and value is not None:
                 value = cPickle.loads(value)
+            if withIdentifier:
+                value = (identifier, value)
             return value
 
         self.log_debug("Getting Cache Token for %r" % (key,))
-        d = self._getMemcacheProtocol().get('%s:%s' % (self._namespace, self._normalizeKey(key)))
-        d.addCallback(_gotit)
+        d = self._getMemcacheProtocol().get('%s:%s' % (self._namespace, self._normalizeKey(key)), withIdentifier=withIdentifier)
+        d.addCallback(_gotit, withIdentifier)
         return d
 
     def delete(self, key):
         self.log_debug("Deleting Cache Token for %r" % (key,))
         return self._getMemcacheProtocol().delete('%s:%s' % (self._namespace, self._normalizeKey(key)))
 
-    def flush_all(self):
+    def flushAll(self):
         self.log_debug("Flushing All Cache Tokens")
-        return self._getMemcacheProtocol().flush_all()
+        return self._getMemcacheProtocol().flushAll()

Modified: CalendarServer/trunk/twistedcaldav/test/test_memcacher.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/test/test_memcacher.py	2011-08-01 23:43:35 UTC (rev 7850)
+++ CalendarServer/trunk/twistedcaldav/test/test_memcacher.py	2011-08-02 06:15:44 UTC (rev 7851)
@@ -148,3 +148,30 @@
         cacher._memcacheProtocol.advanceClock(1)
         result = yield cacher.get("akey")
         self.assertEquals(None, result)
+
+    @inlineCallbacks
+    def test_checkAndSet(self):
+
+        config.ProcessType = "Single"
+        cacher = Memcacher("testing")
+
+        result = yield cacher.set("akey", "avalue")
+        self.assertTrue(result)
+
+        value, identifier = yield cacher.get("akey", withIdentifier=True)
+        self.assertEquals("avalue", value)
+        self.assertEquals(identifier, "0")
+
+        # Make sure cas identifier changes (we know the test implementation increases
+        # by 1 each time)
+        result = yield cacher.set("akey", "anothervalue")
+        value, identifier = yield cacher.get("akey", withIdentifier=True)
+        self.assertEquals("anothervalue", value)
+        self.assertEquals(identifier, "1")
+
+        # Should not work because key doesn't exist:
+        self.assertFalse((yield cacher.checkAndSet("missingkey", "val", "0")))
+        # Should not work because identifier doesn't match:
+        self.assertFalse((yield cacher.checkAndSet("akey", "yetanother", "0")))
+        # Should work because identifier does match:
+        self.assertTrue((yield cacher.checkAndSet("akey", "yetanother", "1")))

Modified: CalendarServer/trunk/txdav/common/datastore/test/util.py
===================================================================
--- CalendarServer/trunk/txdav/common/datastore/test/util.py	2011-08-01 23:43:35 UTC (rev 7850)
+++ CalendarServer/trunk/txdav/common/datastore/test/util.py	2011-08-02 06:15:44 UTC (rev 7851)
@@ -184,11 +184,11 @@
         
         # Deal with memcached items that must be cleared
         from txdav.caldav.datastore.sql import CalendarHome
-        CalendarHome._cacher.flush_all()
+        CalendarHome._cacher.flushAll()
         from txdav.carddav.datastore.sql import AddressBookHome
-        AddressBookHome._cacher.flush_all()
+        AddressBookHome._cacher.flushAll()
         from txdav.base.propertystore.sql import PropertyStore
-        PropertyStore._cacher.flush_all()
+        PropertyStore._cacher.flushAll()
 
 theStoreBuilder = SQLStoreBuilder()
 buildStore = theStoreBuilder.buildStore
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.macosforge.org/pipermail/calendarserver-changes/attachments/20110801/9a702637/attachment-0001.html>


More information about the calendarserver-changes mailing list