[CalendarServer-changes] [13822] CalendarServer/trunk
source_changes at macosforge.org
source_changes at macosforge.org
Fri Aug 1 17:49:24 PDT 2014
Revision: 13822
http://trac.calendarserver.org//changeset/13822
Author: sagen at apple.com
Date: 2014-08-01 17:49:24 -0700 (Fri, 01 Aug 2014)
Log Message:
-----------
Adds directory caching, both in-process and in the directory proxy sidecar. The number of seconds records are cached for in each place are configurable.
Modified Paths:
--------------
CalendarServer/trunk/calendarserver/tap/caldav.py
CalendarServer/trunk/conf/caldavd-test.plist
CalendarServer/trunk/twistedcaldav/directory/test/test_principal.py
CalendarServer/trunk/twistedcaldav/stdconfig.py
CalendarServer/trunk/txdav/common/datastore/test/accounts/accounts.xml
CalendarServer/trunk/txdav/dps/server.py
CalendarServer/trunk/txdav/dps/test/test_client.py
Added Paths:
-----------
CalendarServer/trunk/txdav/who/cache.py
CalendarServer/trunk/txdav/who/test/test_cache.py
Modified: CalendarServer/trunk/calendarserver/tap/caldav.py
===================================================================
--- CalendarServer/trunk/calendarserver/tap/caldav.py 2014-08-01 21:06:17 UTC (rev 13821)
+++ CalendarServer/trunk/calendarserver/tap/caldav.py 2014-08-02 00:49:24 UTC (rev 13822)
@@ -94,9 +94,10 @@
)
from txdav.common.datastore.work.inbox_cleanup import InboxCleanupWork
from txdav.common.datastore.work.revision_cleanup import FindMinValidRevisionWork
-from txdav.who.util import directoryFromConfig
from txdav.dps.client import DirectoryService as DirectoryProxyClientService
+from txdav.who.cache import CachingDirectoryService
from txdav.who.groups import GroupCacher
+from txdav.who.util import directoryFromConfig
from twistedcaldav import memcachepool
from twistedcaldav.config import ConfigurationError
@@ -526,25 +527,24 @@
# Add the directory proxy sidecar first so it at least get spawned
# prior to the caldavd worker processes:
- if config.DirectoryProxy.Enabled:
- log.info("Adding directory proxy service")
+ log.info("Adding directory proxy service")
- dpsArgv = [
- sys.executable,
- sys.argv[0],
- ]
- if config.UserName:
- dpsArgv.extend(("-u", config.UserName))
- if config.GroupName:
- dpsArgv.extend(("-g", config.GroupName))
- dpsArgv.extend((
- "--reactor={}".format(config.Twisted.reactor),
- "-n", "caldav_directoryproxy",
- "-f", self.configPath,
- ))
- self.monitor.addProcess(
- "directoryproxy", dpsArgv, env=PARENT_ENVIRONMENT
- )
+ dpsArgv = [
+ sys.executable,
+ sys.argv[0],
+ ]
+ if config.UserName:
+ dpsArgv.extend(("-u", config.UserName))
+ if config.GroupName:
+ dpsArgv.extend(("-g", config.GroupName))
+ dpsArgv.extend((
+ "--reactor={}".format(config.Twisted.reactor),
+ "-n", "caldav_directoryproxy",
+ "-f", self.configPath,
+ ))
+ self.monitor.addProcess(
+ "directoryproxy", dpsArgv, env=PARENT_ENVIRONMENT
+ )
for slaveNumber in xrange(0, config.MultiProcess.ProcessCount):
if config.UseMetaFD:
@@ -854,6 +854,11 @@
directory = DirectoryProxyClientService(config.DirectoryRealmName)
if config.Servers.Enabled:
directory.setServersDB(buildServersDB(config.Servers.MaxClients))
+ if config.DirectoryProxy.InProcessCachingSeconds:
+ directory = CachingDirectoryService(
+ directory,
+ expireSeconds=config.DirectoryProxy.InProcessCachingSeconds
+ )
store = storeFromConfig(config, txnFactory, directory)
logObserver = AMPCommonAccessLoggingObserver()
result = self.requestProcessingService(options, store, logObserver)
@@ -1491,18 +1496,19 @@
if directory is None:
# Create a Directory Proxy "Server" service and hand it to
# the store.
- # FIXME: right now the store passed *to* the directory is the
- # calendar/contacts data store, but for a multi-server deployment
- # it will need its own separate store.
if config.Servers.Enabled:
serversDB = buildServersDB(config.Servers.MaxClients)
else:
serversDB = None
- store.setDirectoryService(
- directoryFromConfig(
- config, store=store, serversDB=serversDB
+ directorySvc = directoryFromConfig(
+ config, store=store, serversDB=serversDB
+ )
+ if config.DirectoryProxy.InProcessCachingSeconds:
+ directorySvc = CachingDirectoryService(
+ directorySvc,
+ expireSeconds=config.DirectoryProxy.InProcessCachingSeconds
)
- )
+ store.setDirectoryService(directorySvc)
pps = PreProcessingService(
createMainService, cp, store, logObserver, storageService
Modified: CalendarServer/trunk/conf/caldavd-test.plist
===================================================================
--- CalendarServer/trunk/conf/caldavd-test.plist 2014-08-01 21:06:17 UTC (rev 13821)
+++ CalendarServer/trunk/conf/caldavd-test.plist 2014-08-02 00:49:24 UTC (rev 13822)
@@ -175,6 +175,14 @@
<key>MaxAllowedInstances</key>
<integer>3000</integer>
+ <key>DirectoryProxy</key>
+ <dict>
+ <key>InProcessCachingSeconds</key>
+ <integer>10</integer>
+ <key>InSidecarCachingSeconds</key>
+ <integer>30</integer>
+ </dict>
+
<!--
Directory service
@@ -788,7 +796,7 @@
<dict>
<key>Enabled</key>
<true/>
-
+
<!-- Make these short for testing -->
<key>RequestDelaySeconds</key>
<real>0.1</real>
Modified: CalendarServer/trunk/twistedcaldav/directory/test/test_principal.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/directory/test/test_principal.py 2014-08-01 21:06:17 UTC (rev 13821)
+++ CalendarServer/trunk/twistedcaldav/directory/test/test_principal.py 2014-08-02 00:49:24 UTC (rev 13822)
@@ -297,6 +297,12 @@
test_items += (principalURL, alternateURL)
for address in test_items:
+
+ # For txdav.who.test.test_cache I added two accounts which
+ # share the same email address, but that messes up this test.
+ if "mailto:cache-user" in address:
+ continue
+
principal = (
yield provisioningResource
.principalForCalendarUserAddress(address)
Modified: CalendarServer/trunk/twistedcaldav/stdconfig.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/stdconfig.py 2014-08-01 21:06:17 UTC (rev 13821)
+++ CalendarServer/trunk/twistedcaldav/stdconfig.py 2014-08-02 00:49:24 UTC (rev 13822)
@@ -898,8 +898,9 @@
},
"DirectoryProxy": {
- "Enabled": True,
"SocketPath": "directory-proxy.sock",
+ "InProcessCachingSeconds": 10,
+ "InSidecarCachingSeconds": 30,
},
#
Modified: CalendarServer/trunk/txdav/common/datastore/test/accounts/accounts.xml
===================================================================
--- CalendarServer/trunk/txdav/common/datastore/test/accounts/accounts.xml 2014-08-01 21:06:17 UTC (rev 13821)
+++ CalendarServer/trunk/txdav/common/datastore/test/accounts/accounts.xml 2014-08-02 00:49:24 UTC (rev 13822)
@@ -393,6 +393,35 @@
<full-name>Empty Group</full-name>
</record>
+ <!-- Caching Directory test records -->
+
+ <record type="user">
+ <uid>cache-uid-1</uid>
+ <guid>8166C681-2D08-4846-90F7-97023A6EDDC5</guid>
+ <short-name>cache-name-1</short-name>
+ <short-name>cache-alt-name-1</short-name>
+ <password>cache-password-1</password>
+ <full-name>Cache User 1</full-name>
+ <email>cache-user-1 at example.com</email>
+ </record>
+
+ <record type="user">
+ <uid>cache-uid-duplicate-1</uid>
+ <short-name>cache-name-duplicate-1</short-name>
+ <short-name>cache-alt-name-duplicate-1</short-name>
+ <password>cache-password-duplicate-1</password>
+ <full-name>Cache User Duplicate 1</full-name>
+ <email>cache-user-1 at example.com</email>
+ </record>
+
+ <record type="user">
+ <uid>cache-uid-2</uid>
+ <short-name>cache-name-2</short-name>
+ <password>cache-password-2</password>
+ <full-name>Cache User 2</full-name>
+ <email>cache-user-2 at example.com</email>
+ </record>
+
<!-- Calverify test records -->
<record type="user">
Modified: CalendarServer/trunk/txdav/dps/server.py
===================================================================
--- CalendarServer/trunk/txdav/dps/server.py 2014-08-01 21:06:17 UTC (rev 13821)
+++ CalendarServer/trunk/txdav/dps/server.py 2014-08-02 00:49:24 UTC (rev 13822)
@@ -18,6 +18,7 @@
import datetime
import uuid
+from calendarserver.tap.util import getDBPool, storeFromConfig
from twext.python.log import Logger
from twext.who.expression import MatchType, MatchFlags, Operand
from twisted.application import service
@@ -39,6 +40,7 @@
WikiAccessForUIDCommand, ContinuationCommand
# UpdateRecordsCommand, RemoveRecordsCommand
)
+from txdav.who.cache import CachingDirectoryService
from txdav.who.util import directoryFromConfig
from txdav.who.wiki import WikiAccessLevel
from zope.interface import implementer
@@ -571,11 +573,20 @@
setproctitle("CalendarServer Directory Proxy Service")
try:
+ pool, txnFactory = getDBPool(config)
+ store = storeFromConfig(config, txnFactory, None)
directory = directoryFromConfig(config)
+ if config.DirectoryProxy.InSidecarCachingSeconds:
+ directory = CachingDirectoryService(
+ directory,
+ expireSeconds=config.DirectoryProxy.InSidecarCachingSeconds
+ )
+ store.setDirectoryService(directory)
except Exception as e:
log.error("Failed to create directory service", error=e)
raise
+
log.info("Created directory service")
return strPortsService(
Modified: CalendarServer/trunk/txdav/dps/test/test_client.py
===================================================================
--- CalendarServer/trunk/txdav/dps/test/test_client.py 2014-08-01 21:06:17 UTC (rev 13821)
+++ CalendarServer/trunk/txdav/dps/test/test_client.py 2014-08-02 00:49:24 UTC (rev 13822)
@@ -414,7 +414,7 @@
records = (yield self.client.recordsWithRecordType(
RecordType.user
))
- self.assertEquals(len(records), 240)
+ self.assertEquals(len(records), 243)
@inlineCallbacks
Added: CalendarServer/trunk/txdav/who/cache.py
===================================================================
--- CalendarServer/trunk/txdav/who/cache.py (rev 0)
+++ CalendarServer/trunk/txdav/who/cache.py 2014-08-02 00:49:24 UTC (rev 13822)
@@ -0,0 +1,334 @@
+# -*- test-case-name: txdav.who.test.test_cache -*-
+##
+# Copyright (c) 2014 Apple Inc. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+##
+
+"""
+Caching Directory Service
+"""
+
+__all__ = [
+ "CachingDirectoryService",
+]
+
+import time
+
+from zope.interface import implementer
+
+from twisted.internet.defer import inlineCallbacks, returnValue
+from twext.python.log import Logger
+from twext.who.directory import DirectoryService as BaseDirectoryService
+from twext.who.idirectory import (
+ IDirectoryService, FieldName as BaseFieldName
+)
+from twext.who.util import ConstantsContainer
+
+from txdav.common.idirectoryservice import IStoreDirectoryService
+from txdav.who.directory import (
+ CalendarDirectoryServiceMixin,
+)
+from txdav.who.idirectory import (
+ FieldName
+)
+from twisted.python.constants import Values, ValueConstant
+
+log = Logger()
+
+
+class IndexType(Values):
+ """
+ Constants to use for identifying indexes
+ """
+ uid = ValueConstant("uid")
+ guid = ValueConstant("guid")
+ shortName = ValueConstant("shortName")
+ emailAddress = ValueConstant("emailAddress")
+
+
+ at implementer(IDirectoryService, IStoreDirectoryService)
+class CachingDirectoryService(
+ BaseDirectoryService, CalendarDirectoryServiceMixin
+):
+ """
+ Caching directory service.
+
+ This is a directory service that wraps an L{IDirectoryService} and caches
+ directory records.
+ """
+
+ fieldName = ConstantsContainer((
+ BaseFieldName,
+ FieldName,
+ ))
+
+
+
+ def __init__(self, directory, expireSeconds=30):
+ BaseDirectoryService.__init__(self, directory.realmName)
+ self._directory = directory
+ self._expireSeconds = expireSeconds
+ self.resetCache()
+
+
+ def resetCache(self):
+ """
+ Clear the cache
+ """
+ self._cache = {
+ IndexType.uid: {},
+ IndexType.guid: {},
+ IndexType.shortName: {}, # key is (recordType.name, shortName)
+ IndexType.emailAddress: {},
+ }
+ self._hitCount = 0
+ self._requestCount = 0
+
+
+ def setTestTime(self, timestamp):
+ self._test_time = timestamp
+
+
+ def cacheRecord(self, record, indexTypes):
+ """
+ Store a record in the cache, within the specified indexes
+
+ @param record: the directory record
+ @param indexTypes: an iterable of L{IndexType}
+ """
+
+ if hasattr(self, "_test_time"):
+ timestamp = self._test_time
+ else:
+ timestamp = time.time()
+
+ if IndexType.uid in indexTypes:
+ self._cache[IndexType.uid][record.uid] = (timestamp, record)
+
+ if IndexType.guid in indexTypes:
+ try:
+ self._cache[IndexType.guid][record.guid] = (timestamp, record)
+ except AttributeError:
+ pass
+ if IndexType.shortName in indexTypes:
+ try:
+ typeName = record.recordType.name
+ for name in record.shortNames:
+ self._cache[IndexType.shortName][(typeName, name)] = (timestamp, record)
+ except AttributeError:
+ pass
+ if IndexType.emailAddress in indexTypes:
+ try:
+ for emailAddress in record.emailAddresses:
+ self._cache[IndexType.emailAddress][emailAddress] = (timestamp, record)
+ except AttributeError:
+ pass
+
+
+ def lookupRecord(self, indexType, key):
+ """
+ Looks for a record in the specified index, under the specified key
+
+ @param index: an index type
+ @type indexType: L{IndexType}
+
+ @param key: the key to look up in the specified index
+ @type key: any valid type that can be used as a dictionary key
+
+ @return: the cached directory record, or None
+ @rtype: L{DirectoryRecord}
+ """
+
+ self._requestCount += 1
+ if key in self._cache[indexType]:
+
+ if hasattr(self, "_test_time"):
+ now = self._test_time
+ else:
+ now = time.time()
+
+ cachedTime, record = self._cache[indexType].get(key, (0.0, None))
+ if now - self._expireSeconds > cachedTime:
+ log.debug(
+ "Directory cache miss (expired): {index} {key}",
+ index=indexType.value,
+ key=key
+ )
+ # This record has expired
+ del self._cache[indexType][key]
+ return None
+
+ log.debug(
+ "Directory cache hit: {index} {key}",
+ index=indexType.value,
+ key=key
+ )
+ self._hitCount += 1
+ return record
+ else:
+ log.debug(
+ "Directory cache miss: {index} {key}",
+ index=indexType.value,
+ key=key
+ )
+ return None
+
+
+ # Cached methods:
+
+ @inlineCallbacks
+ def recordWithUID(self, uid):
+
+ # First check our cache
+ record = self.lookupRecord(IndexType.uid, uid)
+ if record is None:
+ record = yield self._directory.recordWithUID(uid)
+ if record is not None:
+ # Note we do not index on email address; see below.
+ self.cacheRecord(
+ record,
+ (IndexType.uid, IndexType.guid, IndexType.shortName)
+ )
+
+ returnValue(record)
+
+
+ @inlineCallbacks
+ def recordWithGUID(self, guid):
+
+ # First check our cache
+ record = self.lookupRecord(IndexType.guid, guid)
+ if record is None:
+ record = yield self._directory.recordWithGUID(guid)
+ if record is not None:
+ # Note we do not index on email address; see below.
+ self.cacheRecord(
+ record,
+ (IndexType.uid, IndexType.guid, IndexType.shortName)
+ )
+
+ returnValue(record)
+
+
+ @inlineCallbacks
+ def recordWithShortName(self, recordType, shortName):
+
+ # First check our cache
+ record = self.lookupRecord(
+ IndexType.shortName,
+ (recordType.name, shortName)
+ )
+ if record is None:
+ record = yield self._directory.recordWithShortName(
+ recordType, shortName
+ )
+ if record is not None:
+ # Note we do not index on email address; see below.
+ self.cacheRecord(
+ record,
+ (IndexType.uid, IndexType.guid, IndexType.shortName)
+ )
+
+ returnValue(record)
+
+
+ @inlineCallbacks
+ def recordsWithEmailAddress(self, emailAddress):
+
+ # First check our cache
+ record = self.lookupRecord(IndexType.emailAddress, emailAddress)
+ if record is None:
+ records = yield self._directory.recordsWithEmailAddress(emailAddress)
+ if len(records) == 1:
+ # Only cache if there was a single match (which is the most
+ # common scenario). Caching multiple records for the exact
+ # same key/value complicates the data structures.
+ # Also, this is the only situation where we do index a cached
+ # record on email address. Otherwise, say we had faulted in
+ # on "uid" and then indexed that record on its email address,
+ # the next lookup by email address would only get that record,
+ # but there might be others in the directory service with that
+ # same email address.
+ self.cacheRecord(
+ records[0],
+ (
+ IndexType.uid, IndexType.guid,
+ IndexType.shortName, IndexType.emailAddress
+ )
+ )
+ else:
+ records = [record]
+
+ returnValue(records)
+
+
+ # Uncached methods:
+
+ @property
+ def recordType(self):
+ # Defer to the directory service we're caching
+ return self._directory.recordType
+
+
+ def recordTypes(self):
+ # Defer to the directory service we're caching
+ return self._directory.recordTypes()
+
+
+ def recordsFromExpression(self, expression, recordTypes=None):
+ # Defer to the directory service we're caching
+ return self._directory.recordsFromExpression(
+ expression, recordTypes=recordTypes
+ )
+
+
+ def recordsWithFieldValue(self, fieldName, value):
+ # Defer to the directory service we're caching
+ return self._directory.recordsWithFieldValue(
+ fieldName, value
+ )
+
+
+ def updateRecords(self, records, create=False):
+ # Defer to the directory service we're caching
+ return self._directory.updateRecords(records, create=create)
+
+
+ def removeRecords(self, uids):
+ # Defer to the directory service we're caching
+ return self._directory.removeRecords(uids)
+
+
+ def recordsWithRecordType(self, recordType):
+ # Defer to the directory service we're caching
+ return self._directory.recordsWithRecordType(recordType)
+
+
+ def recordsMatchingTokens(self, *args, **kwds):
+ return CalendarDirectoryServiceMixin.recordsMatchingTokens(
+ self, *args, **kwds
+ )
+
+
+ def recordsMatchingFields(self, *args, **kwds):
+ return CalendarDirectoryServiceMixin.recordsMatchingFields(
+ self, *args, **kwds
+ )
+
+
+ def recordWithCalendarUserAddress(self, *args, **kwds):
+ # This will get cached by the underlying recordWith... call
+ return CalendarDirectoryServiceMixin.recordWithCalendarUserAddress(
+ self, *args, **kwds
+ )
Added: CalendarServer/trunk/txdav/who/test/test_cache.py
===================================================================
--- CalendarServer/trunk/txdav/who/test/test_cache.py (rev 0)
+++ CalendarServer/trunk/txdav/who/test/test_cache.py 2014-08-02 00:49:24 UTC (rev 13822)
@@ -0,0 +1,200 @@
+##
+# Copyright (c) 2014 Apple Inc. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+##
+
+"""
+Caching service tests
+"""
+
+from twisted.internet.defer import inlineCallbacks
+from twistedcaldav.test.util import StoreTestCase
+from txdav.who.cache import CachingDirectoryService
+from twext.who.idirectory import (
+ RecordType
+)
+from txdav.who.idirectory import (
+ RecordType as CalRecordType
+)
+import uuid
+
+
+class CacheTest(StoreTestCase):
+
+ @inlineCallbacks
+ def setUp(self):
+ yield super(CacheTest, self).setUp()
+
+ self.cachingDirectory = CachingDirectoryService(
+ self.directory,
+ expireSeconds=10
+ )
+ self.storeUnderTest().setDirectoryService(self.cachingDirectory)
+
+
+
+ @inlineCallbacks
+ def test_cachingPassThrough(self):
+ """
+ Verify the CachingDirectoryService can pass through method calls to
+ the underlying service.
+ """
+
+ dir = self.cachingDirectory
+
+ self.assertTrue(RecordType.user in dir.recordTypes())
+ self.assertTrue(RecordType.group in dir.recordTypes())
+ self.assertTrue(CalRecordType.location in dir.recordTypes())
+ self.assertTrue(CalRecordType.resource in dir.recordTypes())
+
+ records = yield dir.recordsWithRecordType(RecordType.user)
+ self.assertEquals(len(records), 243)
+
+ record = yield dir.recordWithGUID(uuid.UUID("8166C681-2D08-4846-90F7-97023A6EDDC5"))
+ self.assertEquals(record.uid, u"cache-uid-1")
+
+ record = yield dir.recordWithShortName(RecordType.user, u"cache-name-2")
+ self.assertEquals(record.uid, u"cache-uid-2")
+
+ records = yield dir.recordsWithEmailAddress(u"cache-user-1 at example.com")
+ self.assertEquals(len(records), 2)
+
+ record = yield dir.recordWithCalendarUserAddress(u"mailto:cache-user-2 at example.com")
+ self.assertEquals(record.uid, u"cache-uid-2")
+
+
+
+ @inlineCallbacks
+ def test_cachingHitsAndMisses(self):
+ """
+ Verify faulted in records are indexed appropriately and can be retrieved
+ from the cache even by other attributes.
+ """
+
+ dir = self.cachingDirectory
+
+ # Caching on UID
+ self.assertEquals(dir._hitCount, 0)
+ self.assertEquals(dir._requestCount, 0)
+ record = yield dir.recordWithUID(u"cache-uid-1")
+ self.assertEquals(record.uid, u"cache-uid-1")
+ self.assertEquals(dir._hitCount, 0)
+ self.assertEquals(dir._requestCount, 1)
+
+ # Repeat the same lookup
+ record = yield dir.recordWithUID(u"cache-uid-1")
+ self.assertEquals(record.uid, u"cache-uid-1")
+ self.assertEquals(dir._hitCount, 1)
+ self.assertEquals(dir._requestCount, 2)
+
+ # Lookup the same record, but by GUID
+ record = yield dir.recordWithGUID(uuid.UUID("8166C681-2D08-4846-90F7-97023A6EDDC5"))
+ self.assertEquals(record.uid, u"cache-uid-1")
+ self.assertEquals(dir._hitCount, 2)
+ self.assertEquals(dir._requestCount, 3)
+
+ # Lookup by the shortName for that same record, and it should be a hit
+ record = yield dir.recordWithShortName(RecordType.user, u"cache-name-1")
+ self.assertEquals(record.uid, u"cache-uid-1")
+ self.assertEquals(dir._hitCount, 3)
+ self.assertEquals(dir._requestCount, 4)
+
+ # Now lookup by a different shortName for that same record, and it
+ # should also be a hit
+ record = yield dir.recordWithShortName(RecordType.user, u"cache-alt-name-1")
+ self.assertEquals(record.uid, u"cache-uid-1")
+ self.assertEquals(dir._hitCount, 4)
+ self.assertEquals(dir._requestCount, 5)
+
+
+ dir.resetCache()
+
+ # Look up another record which has a unique email address, first by uid
+ # and then by email address and verify this is a cache miss because we
+ # intentionally don't index on email address when faulting in by another
+ # attribute
+ record = yield dir.recordWithUID(u"cache-uid-2")
+ self.assertEquals(record.uid, u"cache-uid-2")
+ self.assertEquals(dir._hitCount, 0)
+ self.assertEquals(dir._requestCount, 1)
+
+ records = yield dir.recordsWithEmailAddress(u"cache-user-2 at example.com")
+ self.assertEquals(len(records), 1)
+ self.assertEquals(dir._hitCount, 0)
+ self.assertEquals(dir._requestCount, 2)
+
+ records = yield dir.recordsWithEmailAddress(u"cache-user-2 at example.com")
+ self.assertEquals(len(records), 1)
+ self.assertEquals(dir._hitCount, 1)
+ self.assertEquals(dir._requestCount, 3)
+
+ dir.resetCache()
+
+ # Look up a record which has the same email address as another record.
+ record = yield dir.recordWithUID(u"cache-uid-2")
+ self.assertEquals(record.uid, u"cache-uid-2")
+ self.assertEquals(dir._hitCount, 0)
+ self.assertEquals(dir._requestCount, 1)
+
+ # Now lookup by the email address for that record, and it should
+ # be a miss; Note, because there are two records with this email
+ # address, when we repeat this call it will still be a miss because
+ # for simplicity we're only going to cache records when there is a
+ # single result.
+ records = yield dir.recordsWithEmailAddress(u"cache-user-1 at example.com")
+ self.assertEquals(len(records), 2)
+ self.assertEquals(dir._hitCount, 0)
+ self.assertEquals(dir._requestCount, 2)
+
+ records = yield dir.recordsWithEmailAddress(u"cache-user-1 at example.com")
+ self.assertEquals(len(records), 2)
+ self.assertEquals(dir._hitCount, 0)
+ self.assertEquals(dir._requestCount, 3)
+
+
+ @inlineCallbacks
+ def test_cachingExpiration(self):
+ """
+ Verify records expire at the expected time; in these tests, 10 seconds
+ """
+
+ dir = self.cachingDirectory
+
+ dir.setTestTime(1.0)
+
+ record = yield dir.recordWithUID(u"cache-uid-1")
+ self.assertEquals(record.uid, u"cache-uid-1")
+ self.assertEquals(dir._hitCount, 0)
+ self.assertEquals(dir._requestCount, 1)
+
+ # 1 second later, the record is still cached
+ dir.setTestTime(2.0)
+ record = yield dir.recordWithUID(u"cache-uid-1")
+ self.assertEquals(record.uid, u"cache-uid-1")
+ self.assertEquals(dir._hitCount, 1)
+ self.assertEquals(dir._requestCount, 2)
+
+ # 10 seconds later, the record is no longer cached
+ dir.setTestTime(12.0)
+ record = yield dir.recordWithUID(u"cache-uid-1")
+ self.assertEquals(record.uid, u"cache-uid-1")
+ self.assertEquals(dir._hitCount, 1)
+ self.assertEquals(dir._requestCount, 3)
+
+ # Wait another 11 seconds, verify it's not cached by other attributes
+ dir.setTestTime(23.0)
+ record = yield dir.recordWithShortName(RecordType.user, u"cache-alt-name-1")
+ self.assertEquals(record.uid, u"cache-uid-1")
+ self.assertEquals(dir._hitCount, 1)
+ self.assertEquals(dir._requestCount, 4)
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://lists.macosforge.org/pipermail/calendarserver-changes/attachments/20140801/421292f8/attachment-0001.html>
More information about the calendarserver-changes
mailing list