diff options
| author | Chmouel Boudjnah <chmouel@chmouel.com> | 2012-03-02 15:31:54 +0000 |
|---|---|---|
| committer | Jesse Andrews <anotherjesse@gmail.com> | 2012-03-09 21:06:43 -0800 |
| commit | b5c8b3a81911491c97ee95e741c75ffd269f382a (patch) | |
| tree | 123c45ce65859fe26548f9bc2a327ec7b22ec31e | |
| parent | e05bc6a6edeee5e1430e6c36fb38c911821800f5 (diff) | |
Add token caching via memcache.
- Fixes bug 938253
- caching requires both python-memcache and iso8601
Change-Id: I23d5849aad4c6a2333b903eaca6d4f00be8615d3
| -rw-r--r-- | doc/source/nova-api-paste.rst | 2 | ||||
| -rw-r--r-- | doc/source/old/middleware.rst | 2 | ||||
| -rw-r--r-- | keystone/middleware/auth_token.py | 70 |
3 files changed, 72 insertions, 2 deletions
diff --git a/doc/source/nova-api-paste.rst b/doc/source/nova-api-paste.rst index 879cd4a7..d6567144 100644 --- a/doc/source/nova-api-paste.rst +++ b/doc/source/nova-api-paste.rst @@ -140,4 +140,4 @@ nova-api-paste example ;identical to the admin token defined in keystone.conf admin_token = 999888777666 ;Uncomment next line and check ip:port to use memcached to cache token requests - ;memcache_hosts = 127.0.0.1:11211 + ;memcache_servers = 127.0.0.1:11211 diff --git a/doc/source/old/middleware.rst b/doc/source/old/middleware.rst index f5582c79..fd415813 100644 --- a/doc/source/old/middleware.rst +++ b/doc/source/old/middleware.rst @@ -93,7 +93,7 @@ a WSGI component. Example for the auth_token middleware:: auth_uri = http://127.0.0.1:5000/ admin_token = 999888777666 ;Uncomment next line and check ip:port to use memcached to cache token requests - ;memcache_hosts = 127.0.0.1:11211 + ;memcache_servers = 127.0.0.1:11211 *The required configuration entries are:* diff --git a/keystone/middleware/auth_token.py b/keystone/middleware/auth_token.py index 9f9421de..095b6964 100644 --- a/keystone/middleware/auth_token.py +++ b/keystone/middleware/auth_token.py @@ -90,9 +90,11 @@ HTTP_X_ROLE """ +import datetime import httplib import json import logging +import time import webob import webob.exc @@ -143,6 +145,20 @@ class AuthProtocol(object): self.admin_password = conf.get('admin_password') self.admin_tenant_name = conf.get('admin_tenant_name', 'admin') + # Token caching via memcache + self._cache = None + memcache_servers = conf.get('memcache_servers') + # By default the token will be cached for 5 minutes + self.token_cache_time = conf.get('token_cache_time', 300) + if memcache_servers: + try: + import memcache + import iso8601 + logger.info('Using memcache for caching token') + self._cache = memcache.Client(memcache_servers.split(',')) + except NameError as e: + logger.warn('disabled caching due to missing libraries %s', e) + def __call__(self, env, start_response): """Handle incoming request. @@ -317,16 +333,22 @@ class AuthProtocol(object): :raise ServiceError if unable to authenticate token """ + cached = self._cache_get(user_token) + if cached: + return cached + headers = {'X-Auth-Token': self.get_admin_token()} response, data = self._json_request('GET', '/v2.0/tokens/%s' % user_token, additional_headers=headers) if response.status == 200: + self._cache_put(user_token, data) return data if response.status == 404: # FIXME(ja): I'm assuming the 404 status means that user_token is # invalid - not that the admin_token is invalid + self._cache_store_invalid(user_token) raise InvalidUserToken('Token authorization failed') if response.status == 401: logger.info('Keystone rejected admin token, resetting') @@ -419,6 +441,54 @@ class AuthProtocol(object): env_key = self._header_to_env_var(key) return env.get(env_key, default) + def _cache_get(self, token): + """Return token information from cache. + + If token is invalid raise InvalidUserToken + return token only if fresh (not expired). + """ + if self._cache and token: + key = 'tokens/%s' % token + cached = self._cache.get(key) + if cached == 'invalid': + logger.debug('Cached Token %s is marked unauthorized', token) + raise InvalidUserToken('Token authorization failed') + if cached: + data, expires = cached + if time.time() < expires: + logger.debug('Returning cached token %s', token) + return data + else: + logger.debug('Cached Token %s seems expired', token) + + def _cache_put(self, token, data): + """Put token data into the cache. + + Stores the parsed expire date in cache allowing + quick check of token freshness on retrieval. + """ + if self._cache and data: + key = 'tokens/%s' % token + if 'token' in data.get('access', {}): + timestamp = data['access']['token']['expires'] + expires = iso8601.parse_date(timestamp) + else: + logger.error('invalid token format') + return + logger.debug('Storing %s token in memcache', token) + self._cache.set(key, + (data, expires), + time=self.token_cache_time) + + def _cache_store_invalid(self, token): + """Store invalid token in cache.""" + if self._cache: + key = 'tokens/%s' % token + logger.debug('Marking token %s as unauthorized in memcache', token) + self._cache.set(key, + 'invalid', + time=self.token_cache_time) + def filter_factory(global_conf, **local_conf): """Returns a WSGI filter app for use with paste.deploy.""" |
