summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorChmouel Boudjnah <chmouel@chmouel.com>2012-03-02 15:31:54 +0000
committerJesse Andrews <anotherjesse@gmail.com>2012-03-09 21:06:43 -0800
commitb5c8b3a81911491c97ee95e741c75ffd269f382a (patch)
tree123c45ce65859fe26548f9bc2a327ec7b22ec31e
parente05bc6a6edeee5e1430e6c36fb38c911821800f5 (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.rst2
-rw-r--r--doc/source/old/middleware.rst2
-rw-r--r--keystone/middleware/auth_token.py70
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."""