[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