Moved memcached connection in AuthManager to thread-local storage.

Added caching of LDAP connection in thread-local storage.
Optimized LDAP queries, added similar memcached support to LDAPDriver.
Add "per-driver-request" caching of LDAP results. (should be per-api-request)
This commit is contained in:
Yuriy Taraday
2011-05-17 17:38:44 +04:00
parent cdb320870c
commit c68d446324
2 changed files with 98 additions and 15 deletions

View File

@@ -24,7 +24,9 @@ other backends by creating another class that exposes the same
public methods. public methods.
""" """
import functools
import sys import sys
import threading
from nova import exception from nova import exception
from nova import flags from nova import flags
@@ -85,6 +87,7 @@ def _clean(attr):
def sanitize(fn): def sanitize(fn):
"""Decorator to sanitize all args""" """Decorator to sanitize all args"""
@functools.wraps(fn)
def _wrapped(self, *args, **kwargs): def _wrapped(self, *args, **kwargs):
args = [_clean(x) for x in args] args = [_clean(x) for x in args]
kwargs = dict((k, _clean(v)) for (k, v) in kwargs) kwargs = dict((k, _clean(v)) for (k, v) in kwargs)
@@ -103,29 +106,74 @@ class LdapDriver(object):
isadmin_attribute = 'isNovaAdmin' isadmin_attribute = 'isNovaAdmin'
project_attribute = 'owner' project_attribute = 'owner'
project_objectclass = 'groupOfNames' project_objectclass = 'groupOfNames'
__local = threading.local()
def __init__(self): def __init__(self):
"""Imports the LDAP module""" """Imports the LDAP module"""
self.ldap = __import__('ldap') self.ldap = __import__('ldap')
self.conn = None
if FLAGS.ldap_schema_version == 1: if FLAGS.ldap_schema_version == 1:
LdapDriver.project_pattern = '(objectclass=novaProject)' LdapDriver.project_pattern = '(objectclass=novaProject)'
LdapDriver.isadmin_attribute = 'isAdmin' LdapDriver.isadmin_attribute = 'isAdmin'
LdapDriver.project_attribute = 'projectManager' LdapDriver.project_attribute = 'projectManager'
LdapDriver.project_objectclass = 'novaProject' LdapDriver.project_objectclass = 'novaProject'
self.__cache = None
def __enter__(self): def __enter__(self):
"""Creates the connection to LDAP""" """Creates the connection to LDAP"""
self.conn = self.ldap.initialize(FLAGS.ldap_url) # TODO(yorik-sar): Should be per-request cache, not per-driver-request
self.conn.simple_bind_s(FLAGS.ldap_user_dn, FLAGS.ldap_password) self.__cache = {}
return self return self
def __exit__(self, exc_type, exc_value, traceback): def __exit__(self, exc_type, exc_value, traceback):
"""Destroys the connection to LDAP""" """Destroys the connection to LDAP"""
self.conn.unbind_s() self.__cache = None
return False return False
def __local_cache(key_fmt):
"""Wrap function to cache it's result in self.__cache.
Works only with functions with one fixed argument.
"""
def do_wrap(fn):
@functools.wraps(fn)
def inner(self, arg, **kwargs):
cache_key = key_fmt % (arg,)
try:
res = self.__cache[cache_key]
LOG.debug('Local cache hit for %s by key %s' %
(fn.__name__, cache_key))
return res
except KeyError:
res = fn(self, arg, **kwargs)
self.__cache[cache_key] = res
return res
return inner
return do_wrap
@property
def conn(self):
try:
return self.__local.conn
except AttributeError:
conn = self.ldap.initialize(FLAGS.ldap_url)
conn.simple_bind_s(FLAGS.ldap_user_dn, FLAGS.ldap_password)
self.__local.conn = conn
return conn
@property
def mc(self):
try:
return self.__local.mc
except AttributeError:
if FLAGS.memcached_servers:
import memcache
else:
from nova import fakememcache as memcache
mc = memcache.Client(FLAGS.memcached_servers, debug=0)
self.__local.mc = mc
return mc
@sanitize @sanitize
@__local_cache('uid_user-%s')
def get_user(self, uid): def get_user(self, uid):
"""Retrieve user by id""" """Retrieve user by id"""
attr = self.__get_ldap_user(uid) attr = self.__get_ldap_user(uid)
@@ -134,15 +182,30 @@ class LdapDriver(object):
@sanitize @sanitize
def get_user_from_access_key(self, access): def get_user_from_access_key(self, access):
"""Retrieve user by access key""" """Retrieve user by access key"""
cache_key = 'uak_dn_%s'%(access,)
user_dn = self.mc.get(cache_key)
if user_dn:
user = self.__to_user(
self.__find_object(user_dn, scope=self.ldap.SCOPE_BASE))
if user:
if user['access'] == access:
return user
else:
self.mc.set(cache_key, None)
query = '(accessKey=%s)' % access query = '(accessKey=%s)' % access
dn = FLAGS.ldap_user_subtree dn = FLAGS.ldap_user_subtree
return self.__to_user(self.__find_object(dn, query)) user_obj = self.__find_object(dn, query)
user = self.__to_user(user_obj)
if user:
self.mc.set(cache_key, user_obj['dn'][0])
return user
@sanitize @sanitize
@__local_cache('pid_project-%s')
def get_project(self, pid): def get_project(self, pid):
"""Retrieve project by id""" """Retrieve project by id"""
dn = self.__project_to_dn(pid) dn = self.__project_to_dn(pid, search=False)
attr = self.__find_object(dn, LdapDriver.project_pattern) attr = self.__find_object(dn, LdapDriver.project_pattern, scope=self.ldap.SCOPE_BASE)
return self.__to_project(attr) return self.__to_project(attr)
@sanitize @sanitize
@@ -395,6 +458,7 @@ class LdapDriver(object):
"""Check if project exists""" """Check if project exists"""
return self.get_project(project_id) is not None return self.get_project(project_id) is not None
@__local_cache('uid_attrs-%s')
def __get_ldap_user(self, uid): def __get_ldap_user(self, uid):
"""Retrieve LDAP user entry by id""" """Retrieve LDAP user entry by id"""
dn = FLAGS.ldap_user_subtree dn = FLAGS.ldap_user_subtree
@@ -426,12 +490,20 @@ class LdapDriver(object):
if scope is None: if scope is None:
# One of the flags is 0! # One of the flags is 0!
scope = self.ldap.SCOPE_SUBTREE scope = self.ldap.SCOPE_SUBTREE
if query is None:
query = "(objectClass=*)"
try: try:
res = self.conn.search_s(dn, scope, query) res = self.conn.search_s(dn, scope, query)
except self.ldap.NO_SUCH_OBJECT: except self.ldap.NO_SUCH_OBJECT:
return [] return []
# Just return the attributes # Just return the attributes
return [attributes for dn, attributes in res] # FIXME(yorik-sar): Whole driver should be refactored to
# prevent this hack
res1 = []
for dn, attrs in res:
attrs['dn'] = [dn]
res1.append(attrs)
return res1
def __find_role_dns(self, tree): def __find_role_dns(self, tree):
"""Find dns of role objects in given tree""" """Find dns of role objects in given tree"""
@@ -564,6 +636,7 @@ class LdapDriver(object):
'description': attr.get('description', [None])[0], 'description': attr.get('description', [None])[0],
'member_ids': [self.__dn_to_uid(x) for x in member_dns]} 'member_ids': [self.__dn_to_uid(x) for x in member_dns]}
@__local_cache('uid_dn-%s')
def __uid_to_dn(self, uid, search=True): def __uid_to_dn(self, uid, search=True):
"""Convert uid to dn""" """Convert uid to dn"""
# By default return a generated DN # By default return a generated DN
@@ -576,6 +649,7 @@ class LdapDriver(object):
userdn = user[0] userdn = user[0]
return userdn return userdn
@__local_cache('pid_dn-%s')
def __project_to_dn(self, pid, search=True): def __project_to_dn(self, pid, search=True):
"""Convert pid to dn""" """Convert pid to dn"""
# By default return a generated DN # By default return a generated DN
@@ -603,10 +677,11 @@ class LdapDriver(object):
else: else:
return None return None
@__local_cache('dn_uid-%s')
def __dn_to_uid(self, dn): def __dn_to_uid(self, dn):
"""Convert user dn to uid""" """Convert user dn to uid"""
query = '(objectclass=novaUser)' query = '(objectclass=novaUser)'
user = self.__find_object(dn, query) user = self.__find_object(dn, query, scope=self.ldap.SCOPE_BASE)
return user[FLAGS.ldap_user_id_attribute][0] return user[FLAGS.ldap_user_id_attribute][0]

View File

@@ -23,6 +23,7 @@ Nova authentication management
import os import os
import shutil import shutil
import string # pylint: disable=W0402 import string # pylint: disable=W0402
import threading
import tempfile import tempfile
import uuid import uuid
import zipfile import zipfile
@@ -206,6 +207,7 @@ class AuthManager(object):
""" """
_instance = None _instance = None
__local = threading.local()
def __new__(cls, *args, **kwargs): def __new__(cls, *args, **kwargs):
"""Returns the AuthManager singleton""" """Returns the AuthManager singleton"""
@@ -223,12 +225,18 @@ class AuthManager(object):
if driver or not getattr(self, 'driver', None): if driver or not getattr(self, 'driver', None):
self.driver = utils.import_class(driver or FLAGS.auth_driver) self.driver = utils.import_class(driver or FLAGS.auth_driver)
@property
def mc(self):
try:
return self.__local.mc
except AttributeError:
if FLAGS.memcached_servers: if FLAGS.memcached_servers:
import memcache import memcache
else: else:
from nova import fakememcache as memcache from nova import fakememcache as memcache
self.mc = memcache.Client(FLAGS.memcached_servers, mc = memcache.Client(FLAGS.memcached_servers, debug=0)
debug=0) self.__local.mc = mc
return mc
def authenticate(self, access, signature, params, verb='GET', def authenticate(self, access, signature, params, verb='GET',
server_string='127.0.0.1:8773', path='/', server_string='127.0.0.1:8773', path='/',