[CalendarServer-changes] [4653] CalendarServer/branches/users/wsanchez/deployment

source_changes at macosforge.org source_changes at macosforge.org
Wed Oct 28 12:08:06 PDT 2009


Revision: 4653
          http://trac.macosforge.org/projects/calendarserver/changeset/4653
Author:   sagen at apple.com
Date:     2009-10-28 12:08:05 -0700 (Wed, 28 Oct 2009)
Log Message:
-----------
Adding a Basic auth caching mechanism to cut down on the traffic to OD

Modified Paths:
--------------
    CalendarServer/branches/users/wsanchez/deployment/memcacheclient.py
    CalendarServer/branches/users/wsanchez/deployment/twistedcaldav/directory/appleopendirectory.py
    CalendarServer/branches/users/wsanchez/deployment/twistedcaldav/directory/test/test_digest.py
    CalendarServer/branches/users/wsanchez/deployment/twistedcaldav/directory/test/test_opendirectoryrecords.py
    CalendarServer/branches/users/wsanchez/deployment/twistedcaldav/test/test_memcacher.py
    CalendarServer/branches/users/wsanchez/deployment/twistedcaldav/test/test_resource.py
    CalendarServer/branches/users/wsanchez/deployment/twistedcaldav/test/test_root.py
    CalendarServer/branches/users/wsanchez/deployment/twistedcaldav/test/test_static.py
    CalendarServer/branches/users/wsanchez/deployment/twistedcaldav/test/test_tap.py

Modified: CalendarServer/branches/users/wsanchez/deployment/memcacheclient.py
===================================================================
--- CalendarServer/branches/users/wsanchez/deployment/memcacheclient.py	2009-10-28 19:00:37 UTC (rev 4652)
+++ CalendarServer/branches/users/wsanchez/deployment/memcacheclient.py	2009-10-28 19:08:05 UTC (rev 4653)
@@ -1274,6 +1274,9 @@
         Is not a string (Raises MemcachedKeyError)
         Is None (Raises MemcachedKeyError)
     """
+
+    return # Short circuit this expensive method
+
     if type(key) == types.TupleType: key = key[1]
     if not key:
         raise Client.MemcachedKeyNoneError, ("Key is None")

Modified: CalendarServer/branches/users/wsanchez/deployment/twistedcaldav/directory/appleopendirectory.py
===================================================================
--- CalendarServer/branches/users/wsanchez/deployment/twistedcaldav/directory/appleopendirectory.py	2009-10-28 19:00:37 UTC (rev 4652)
+++ CalendarServer/branches/users/wsanchez/deployment/twistedcaldav/directory/appleopendirectory.py	2009-10-28 19:08:05 UTC (rev 4653)
@@ -33,7 +33,14 @@
 import opendirectory
 import dsattributes
 import dsquery
+import memcacheclient
 
+try:
+    from hashlib import md5
+except ImportError:
+    from md5 import new as md5
+
+
 from twisted.internet.reactor import callLater
 from twisted.internet.threads import deferToThread
 from twisted.cred.credentials import UsernamePassword
@@ -174,6 +181,14 @@
             h = (h + hash(getattr(self, attr))) & sys.maxint
         return h
 
+    def _getMemcacheClient(self, refresh=False):
+        if refresh or not hasattr(self, "memcacheClient"):
+            self.memcacheClient = memcacheclient.ClientFactory.getClient(['%s:%s' %
+                (config.Memcached.BindAddress, config.Memcached.Port)],
+                debug=0, pickleProtocol=2)
+        return self.memcacheClient
+
+
     def _lookupVHostRecord(self):
         """
         Get the OD service record for this host.
@@ -1087,20 +1102,36 @@
         result.update(self.service.readOnlyProxiesForGUID(DirectoryService.recordType_locations, self.guid))
         return result
 
+
+    def getMemcacheKey(self, shortName):
+        key = "auth-%s" % (md5(shortName).hexdigest(),)
+        return key
+
     def verifyCredentials(self, credentials):
         if isinstance(credentials, UsernamePassword):
-            # Check cached password
+            # Check cached password which is an md5 hexdigest
+
+            credPassword = md5(credentials.password).hexdigest()
             try:
-                if credentials.password == self.password:
+                if credPassword == self.password:
                     return True
             except AttributeError:
-                pass
+                # No locally stored password; check memcached
+                key = self.getMemcacheKey(self.shortName)
+                memcachePassword = self.service._getMemcacheClient().get(key)
+                if memcachePassword is not None:
+                    if credPassword == memcachePassword:
+                        # Memcached version matches, store locally
+                        self.password = credPassword
+                        return True
 
-            # Check with directory services
+            # No local version, *or* local version differs; check directory services
             try:
                 if opendirectory.authenticateUserBasic(self.service.directory, self.nodeName, self.shortName, credentials.password):
                     # Cache the password to avoid future DS queries
-                    self.password = credentials.password
+                    self.password = credPassword
+                    key = self.getMemcacheKey(self.shortName)
+                    self.service._getMemcacheClient().set(key, self.password, time=self.service.cacheTimeout*60)
                     return True
             except opendirectory.ODError, e:
                 self.log_error("Open Directory (node=%s) error while performing basic authentication for user %s: %s"

Modified: CalendarServer/branches/users/wsanchez/deployment/twistedcaldav/directory/test/test_digest.py
===================================================================
--- CalendarServer/branches/users/wsanchez/deployment/twistedcaldav/directory/test/test_digest.py	2009-10-28 19:00:37 UTC (rev 4652)
+++ CalendarServer/branches/users/wsanchez/deployment/twistedcaldav/directory/test/test_digest.py	2009-10-28 19:08:05 UTC (rev 4653)
@@ -2,7 +2,7 @@
 
 from twisted.cred import error
 from twisted.internet import address
-from twisted.trial import unittest
+from twistedcaldav.test.util import TestCase
 from twisted.web2.auth import digest
 from twisted.web2.auth.wrapper import UnauthorizedResponse
 from twisted.web2.test.test_server import SimpleRequest
@@ -71,7 +71,7 @@
 emtpyAttributeAuthRequest = 'realm="",nonce="doesn\'t matter"'
 
 
-class DigestAuthTestCase(unittest.TestCase):
+class DigestAuthTestCase(TestCase):
     """
     Test the behavior of DigestCredentialFactory
     """
@@ -80,6 +80,7 @@
         """
         Create a DigestCredentialFactory for testing
         """
+        super(DigestAuthTestCase, self).setUp()
         self.path1 = self.mktemp()
         self.path2 = self.mktemp()
         os.mkdir(self.path1)

Modified: CalendarServer/branches/users/wsanchez/deployment/twistedcaldav/directory/test/test_opendirectoryrecords.py
===================================================================
--- CalendarServer/branches/users/wsanchez/deployment/twistedcaldav/directory/test/test_opendirectoryrecords.py	2009-10-28 19:00:37 UTC (rev 4652)
+++ CalendarServer/branches/users/wsanchez/deployment/twistedcaldav/directory/test/test_opendirectoryrecords.py	2009-10-28 19:08:05 UTC (rev 4653)
@@ -14,8 +14,12 @@
 # limitations under the License.
 ##
 
-import twisted.trial.unittest
+from twistedcaldav.test.util import TestCase
+from twisted.cred.credentials import UsernamePassword
+import opendirectory
 
+
+
 try:
     from twistedcaldav.directory.appleopendirectory import OpenDirectoryService as RealOpenDirectoryService
     import dsattributes
@@ -42,7 +46,7 @@
 
             return tuple(records)
     
-    class ReloadCache(twisted.trial.unittest.TestCase):
+    class ReloadCache(TestCase):
         def setUp(self):
             super(ReloadCache, self).setUp()
             self._service = OpenDirectoryService(node="/Search", dosetup=False)
@@ -449,6 +453,71 @@
             user3 = self._service.recordWithCalendarUserAddress("mailto:user03 at example.com")
             self.assertTrue(user3 is not None)
 
+
+    class AuthCacheTests(TestCase):
+
+        def _authenticateUserBasic(self, ignore, node, name, password):
+            self._odAccessed = True
+            return self._passwords.get(name, "") == password
+
+        def setUp(self):
+            super(AuthCacheTests, self).setUp()
+            self._service = OpenDirectoryService(node="/Search", dosetup=False)
+            self._service.servicetags.add("FE588D50-0514-4DF9-BCB5-8ECA5F3DA274:030572AE-ABEC-4E0F-83C9-FCA304769E5F:calendar")
+            self._passwords = { }
+
+            # Monkeypatch the real opendirectory.authenticateUserBasic
+            self._prev = opendirectory.authenticateUserBasic
+            opendirectory.authenticateUserBasic = self._authenticateUserBasic
+
+        def tearDown(self):
+            for call in self._service._delayedCalls:
+                call.cancel()
+            opendirectory.authenticateUserBasic = self._prev
+
+        def test_caching(self):
+
+            self._service.fakerecords = {
+                DirectoryService.recordType_users: [
+                    fakeODRecord("User 01"),
+                ],
+            }
+            self._service.reloadCache(DirectoryService.recordType_users)
+
+            user1 = self._service.recordWithCalendarUserAddress("mailto:user01 at example.com")
+            self._passwords["user01"] = "user01"
+            cred = UsernamePassword("user01", "user01")
+
+            # Verify we go to OD when password is neither local nor in memcache
+            self._odAccessed = False
+            self.assertTrue(user1.verifyCredentials(cred))
+            self.assertTrue(self._odAccessed)
+
+            # Verify we don't go to OD when password is local
+            self._odAccessed = False
+            self.assertTrue(user1.verifyCredentials(cred))
+            self.assertFalse(self._odAccessed)
+
+            # Verify we don't go to OD when password not local but *is* in
+            # memcache
+            del user1.password
+            self._odAccessed = False
+            self.assertTrue(user1.verifyCredentials(cred))
+            self.assertFalse(self._odAccessed)
+
+            # Verify a password change will send us to OD
+            self._passwords["user01"] = "new user01"
+            cred = UsernamePassword("user01", "new user01")
+            self._odAccessed = False
+            self.assertTrue(user1.verifyCredentials(cred))
+            self.assertTrue(self._odAccessed)
+
+            # Verify the new password was memcached correctly
+            del user1.password
+            self._odAccessed = False
+            self.assertTrue(user1.verifyCredentials(cred))
+            self.assertFalse(self._odAccessed)
+
 def fakeODRecord(fullName, shortName=None, guid=None, email=None, addLocator=True, members=None):
     if shortName is None:
         shortName = shortNameForFullName(fullName)

Modified: CalendarServer/branches/users/wsanchez/deployment/twistedcaldav/test/test_memcacher.py
===================================================================
--- CalendarServer/branches/users/wsanchez/deployment/twistedcaldav/test/test_memcacher.py	2009-10-28 19:00:37 UTC (rev 4652)
+++ CalendarServer/branches/users/wsanchez/deployment/twistedcaldav/test/test_memcacher.py	2009-10-28 19:08:05 UTC (rev 4653)
@@ -6,7 +6,7 @@
 """
 
 from twisted.internet.defer import inlineCallbacks
-from twisted.trial.unittest import TestCase
+from twistedcaldav.test.util import TestCase
 
 from twistedcaldav.config import config
 from twistedcaldav.memcacher import Memcacher

Modified: CalendarServer/branches/users/wsanchez/deployment/twistedcaldav/test/test_resource.py
===================================================================
--- CalendarServer/branches/users/wsanchez/deployment/twistedcaldav/test/test_resource.py	2009-10-28 19:00:37 UTC (rev 4652)
+++ CalendarServer/branches/users/wsanchez/deployment/twistedcaldav/test/test_resource.py	2009-10-28 19:08:05 UTC (rev 4653)
@@ -14,7 +14,7 @@
 # limitations under the License.
 ##
 
-from twisted.trial.unittest import TestCase
+from twistedcaldav.test.util import TestCase
 
 from twistedcaldav.resource import CalDAVResource
 
@@ -28,6 +28,7 @@
 
 class CalDAVResourceTests(TestCase):
     def setUp(self):
+        super(CalDAVResourceTests, self).setUp()
         self.resource = CalDAVResource()
         self.resource._dead_properties = InMemoryPropertyStore()
 

Modified: CalendarServer/branches/users/wsanchez/deployment/twistedcaldav/test/test_root.py
===================================================================
--- CalendarServer/branches/users/wsanchez/deployment/twistedcaldav/test/test_root.py	2009-10-28 19:00:37 UTC (rev 4652)
+++ CalendarServer/branches/users/wsanchez/deployment/twistedcaldav/test/test_root.py	2009-10-28 19:08:05 UTC (rev 4653)
@@ -52,6 +52,7 @@
 
 class RootTests(TestCase):
     def setUp(self):
+        super(RootTests, self).setUp()
         self.docroot = self.mktemp()
         os.mkdir(self.docroot)
 

Modified: CalendarServer/branches/users/wsanchez/deployment/twistedcaldav/test/test_static.py
===================================================================
--- CalendarServer/branches/users/wsanchez/deployment/twistedcaldav/test/test_static.py	2009-10-28 19:00:37 UTC (rev 4652)
+++ CalendarServer/branches/users/wsanchez/deployment/twistedcaldav/test/test_static.py	2009-10-28 19:08:05 UTC (rev 4653)
@@ -14,7 +14,7 @@
 # limitations under the License.
 ##
 
-from twisted.trial.unittest import TestCase
+from twistedcaldav.test.util import TestCase
 
 from twistedcaldav.static import CalendarHomeFile, CalDAVFile
 from twistedcaldav.cache import DisabledCacheNotifier
@@ -27,6 +27,7 @@
 
 class CalendarHomeFileTests(TestCase):
     def setUp(self):
+        super(CalendarHomeFileTests, self).setUp()
         self.calendarHome = CalendarHomeFile(self.mktemp(),
                                              StubParentResource(),
                                              object())
@@ -44,6 +45,7 @@
 
 class CalDAVFileTests(TestCase):
     def setUp(self):
+        super(CalDAVFileTests, self).setUp()
         self.caldavFile = CalDAVFile(self.mktemp())
         self.caldavFile.fp.createDirectory()
         self.caldavFile.cacheNotifier = StubCacheChangeNotifier()

Modified: CalendarServer/branches/users/wsanchez/deployment/twistedcaldav/test/test_tap.py
===================================================================
--- CalendarServer/branches/users/wsanchez/deployment/twistedcaldav/test/test_tap.py	2009-10-28 19:00:37 UTC (rev 4652)
+++ CalendarServer/branches/users/wsanchez/deployment/twistedcaldav/test/test_tap.py	2009-10-28 19:08:05 UTC (rev 4653)
@@ -18,6 +18,7 @@
 from copy import deepcopy
 
 from twisted.trial import unittest
+from twistedcaldav.test.util import TestCase
 
 from twisted.python.usage import Options, UsageError
 from twisted.python.util import sibpath
@@ -53,7 +54,7 @@
         pass
 
 
-class CalDAVOptionsTest(unittest.TestCase):
+class CalDAVOptionsTest(TestCase):
     """
     Test various parameters of our usage.Options subclass
     """
@@ -63,6 +64,7 @@
         Set up our options object, giving it a parent, and forcing the
         global config to be loaded from defaults.
         """
+        super(CalDAVOptionsTest, self).setUp()
         self.config = TestCalDAVOptions()
         self.config.parent = Options()
         self.config.parent['uid'] = 0
@@ -163,7 +165,7 @@
 
         self.assertEquals(config.MultiProcess['ProcessCount'], 102)
 
-class BaseServiceMakerTests(unittest.TestCase):
+class BaseServiceMakerTests(TestCase):
     """
     Utility class for ServiceMaker tests.
     """
@@ -171,6 +173,7 @@
     configOptions = None
 
     def setUp(self):
+        super(BaseServiceMakerTests, self).setUp()
         self.options = TestCalDAVOptions()
         self.options.parent = Options()
         self.options.parent['gid'] = None
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.macosforge.org/pipermail/calendarserver-changes/attachments/20091028/97d80343/attachment-0001.html>


More information about the calendarserver-changes mailing list