您的位置:首页 > 运维架构

Openstack Keystone 认证流程(七)--API 及 Driver

2015-02-16 15:26 447 查看
在前面几章中, 我们基本上都过了Keystone处理的所有流程,但是还有两个小问题没有解决,现在就来解决它们。

1. identity_api

在keystone.token.controllers.Auth.authenticate方法中, 我们有用到identity_api, 但是却没有地方来初始它。回到Auth类定义的地方。

@dependency.requires('token_provider_api')
class Auth(controller.V2Controller):
...
@dependency.requires('identity_api', 'policy_api', 'token_api',
'trust_api', 'catalog_api', 'credential_api',
'assignment_api')
class V2Controller(wsgi.Application):


在Auth类的父类V2Controller中, 可以看到@dependency.requires装饰器被使用到,再来看看这个装饰器的实现。

def requires(*dependencies):
"""Inject specified dependencies from the registry into the instance."""
def wrapper(self, *args, **kwargs):
"""Inject each dependency from the registry."""
self.__wrapped_init__(*args, **kwargs)
_process_dependencies(self)

def wrapped(cls):
"""Note the required dependencies on the object for later injection.

The dependencies of the parent class are combined with that of the
child class to create a new set of dependencies.
"""
existing_dependencies = getattr(cls, '_dependencies', set())
cls._dependencies = existing_dependencies.union(dependencies)
if not hasattr(cls, '__wrapped_init__'):
cls.__wrapped_init__ = cls.__init__
cls.__init__ = wrapper
return cls

return wrapped


可以看到这个装饰器返回的是wrapped, 再看wrapped的实现, 它先把当前类的已有的依赖取出来, 如果没有的话,设置为一个空的set(). 如果存在,则把它和当前的dependencies合并且只保留一份。接下来, 把class的__init__替换为wrapper, 并且把__wrapped_init__ 替换为原来的__init__。这样在生成类对象时,就会调用到wrapper,在wrapper中,但先调用了__wrapped_init__ 方法,也就是类的原始__init__ 方法,然后再调用_process_dependencies来处理依赖库。_process_dependencies的代码如下:

REGISTRY = {}
_future_dependencies = {}
_future_optionals = {}
...
def _process_dependencies(obj):
def process(obj, attr_name, unresolved_in_out):
for dependency in getattr(obj, attr_name, []):
if dependency not in REGISTRY:
# We don't know about this dependency, so save it for later.
unresolved_in_out.setdefault(dependency, []).append(obj)
continue

setattr(obj, dependency, REGISTRY[dependency])

process(obj, '_dependencies', _future_dependencies)
process(obj, '_optionals', _future_optionals)


在这个方法中, 我们先不考虑_optionals,其实它们是相似的东西。它会调用process,然后对类的依赖库进行初始化。在process中,先从当前类的_dependencies中取出所有的依赖,然后一个个去查询REGISTRY中有没有对应的库,如果有,就把它设为的一个属性。否则把它放到_future_dependencies变量中,留到后面进行处理。

到这里就只剩下一个问题, REGISTRY中的库又是什么时候给加进来的?

在同一个文件中,可以看到如下方法:

def provider(name):
"""Register the wrapped dependency provider under the specified name."""
def wrapper(cls):
def wrapped(init):
def __wrapped_init__(self, *args, **kwargs):
"""Initialize the wrapped object and add it to the registry."""
init(self, *args, **kwargs)
REGISTRY[name] = self

resolve_future_dependencies(name)

return __wrapped_init__

cls.__init__ = wrapped(cls.__init__)
return cls
return wrapper


可以看出,这也是一个装饰器, 它把类的__init__ 方法替换为__wrapped_init__ 。 也就是说, 在生成类的实例时,__wrapped_init__ 会被调用, 然后先调用类原始的__init__ ,再把当前实例加到REGISTRY中,并且调用resolve_future_dependencies, 看到这里, 可以看出resolve_future_dependencies是用来处理当时存放在_future_dependencies的依赖库。它的代码如下:

def resolve_future_dependencies(provider_name=None):
if provider_name:
# A provider was registered, so take care of any objects depending on
# it.
targets = _future_dependencies.pop(provider_name, [])
targets.extend(_future_optionals.pop(provider_name, []))

for target in targets:
setattr(target, provider_name, REGISTRY[provider_name])

return

# Resolve optional dependencies, sets the attribute to None if there's no
# provider registered.
for dependency, targets in _future_optionals.iteritems():
provider = REGISTRY.get(dependency)
for target in targets:
setattr(target, dependency, provider)

_future_optionals.clear()

# Resolve optional dependencies, raises UnresolvableDependencyException if
# there's no provider registered.
try:
for dependency, targets in _future_dependencies.iteritems():
if dependency not in REGISTRY:
raise UnresolvableDependencyException(dependency)

for target in targets:
setattr(target, dependency, REGISTRY[dependency])
finally:
_future_dependencies.clear()


在这里,我们是有传入名称的,所以实际的代码只有以下几行

if provider_name:
# A provider was registered, so take care of any objects depending on
# it.
targets = _future_dependencies.pop(provider_name, [])
targets.extend(_future_optionals.pop(provider_name, []))

for target in targets:
setattr(target, provider_name, REGISTRY[provider_name])

return


可以看出, 其实就是从_future_dependencies取出对应的依赖库名,然后把它设置为原来require所对应的类的实例。这样原来的类实例就可以直接访问这个属性了。

万事具备,只欠东风。就看这个identity_api由谁来提供了。

在identity/core.py中, 可以看到其Manager的定义类如下:

@dependency.provider('identity_api')
@dependency.requires('assignment_api')
class Manager(manager.Manager):


2. driver 初始化

在需要使用数据库的地方,其定义如下,可以看出,这里有使用了装饰器。

@domains_configured
def authenticate(self, user_id, password, domain_scope=None):
domain_id, driver = self._get_domain_id_and_driver(domain_scope)
ref = driver.authenticate(user_id, password)
if not driver.is_domain_aware():
ref = self._set_domain_id(ref, domain_id)
return ref


转到装饰器的代码

def domains_configured(f):
@functools.wraps(f)
def wrapper(self, *args, **kwargs):
if (not self.domain_configs.configured and
CONF.identity.domain_specific_drivers_enabled):
LOG.warning(_(
'Running an experimental and unsupported configuration '
'(domain_specific_drivers_enabled = True); '
'this will result in known issues.'))
self.domain_configs.setup_domain_drivers(
self.driver, self.assignment_api)
return f(self, *args, **kwargs)
return wrapper


这里假设系统中没有使用基于domain的配置文件,可以看出它使用的是self.driver。这个在类的初始化方法中可以看到

def __init__(self):
super(Manager, self).__init__(CONF.identity.driver)
self.domain_configs = DomainConfigs()


对应的配置信息:

[identity]

driver = keystone.identity.backends.sql.Identity

所以其使用的是keystone.identity.backends.sql.Identity

在这里面,使用的是基于ORM的数据库LIB sqlalchemy

具体原理可以参考上面的引用。我们可以看看user表的实现:

class User(sql.ModelBase, sql.DictBase):
__tablename__ = 'user'
attributes = ['id', 'name', 'domain_id', 'password', 'enabled',
'default_project_id']
id = sql.Column(sql.String(64), primary_key=True)
name = sql.Column(sql.String(255), nullable=False)
domain_id = sql.Column(sql.String(64), sql.ForeignKey('domain.id'),
nullable=False)
password = sql.Column(sql.String(128))
enabled = sql.Column(sql.Boolean)
extra = sql.Column(sql.JsonBlob())
default_project_id = sql.Column(sql.String(64))
# Unique constraint across two columns to create the separation
# rather than just only 'name' being unique
__table_args__ = (sql.UniqueConstraint('domain_id', 'name'), {})

def to_dict(self, include_extra_dict=False):
d = super(User, self).to_dict(include_extra_dict=include_extra_dict)
if 'default_project_id' in d and d['default_project_id'] is None:
del d['default_project_id']
return d
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: