Openstack Keystone 认证流程(七)--API 及 Driver
2015-02-16 15:26
447 查看
在前面几章中, 我们基本上都过了Keystone处理的所有流程,但是还有两个小问题没有解决,现在就来解决它们。
在Auth类的父类V2Controller中, 可以看到@dependency.requires装饰器被使用到,再来看看这个装饰器的实现。
可以看到这个装饰器返回的是wrapped, 再看wrapped的实现, 它先把当前类的已有的依赖取出来, 如果没有的话,设置为一个空的set(). 如果存在,则把它和当前的dependencies合并且只保留一份。接下来, 把class的__init__替换为wrapper, 并且把__wrapped_init__ 替换为原来的__init__。这样在生成类对象时,就会调用到wrapper,在wrapper中,但先调用了__wrapped_init__ 方法,也就是类的原始__init__ 方法,然后再调用_process_dependencies来处理依赖库。_process_dependencies的代码如下:
在这个方法中, 我们先不考虑_optionals,其实它们是相似的东西。它会调用process,然后对类的依赖库进行初始化。在process中,先从当前类的_dependencies中取出所有的依赖,然后一个个去查询REGISTRY中有没有对应的库,如果有,就把它设为的一个属性。否则把它放到_future_dependencies变量中,留到后面进行处理。
到这里就只剩下一个问题, REGISTRY中的库又是什么时候给加进来的?
在同一个文件中,可以看到如下方法:
可以看出,这也是一个装饰器, 它把类的__init__ 方法替换为__wrapped_init__ 。 也就是说, 在生成类的实例时,__wrapped_init__ 会被调用, 然后先调用类原始的__init__ ,再把当前实例加到REGISTRY中,并且调用resolve_future_dependencies, 看到这里, 可以看出resolve_future_dependencies是用来处理当时存放在_future_dependencies的依赖库。它的代码如下:
在这里,我们是有传入名称的,所以实际的代码只有以下几行
可以看出, 其实就是从_future_dependencies取出对应的依赖库名,然后把它设置为原来require所对应的类的实例。这样原来的类实例就可以直接访问这个属性了。
万事具备,只欠东风。就看这个identity_api由谁来提供了。
在identity/core.py中, 可以看到其Manager的定义类如下:
转到装饰器的代码
这里假设系统中没有使用基于domain的配置文件,可以看出它使用的是self.driver。这个在类的初始化方法中可以看到
对应的配置信息:
[identity]
driver = keystone.identity.backends.sql.Identity
所以其使用的是keystone.identity.backends.sql.Identity
在这里面,使用的是基于ORM的数据库LIB sqlalchemy
具体原理可以参考上面的引用。我们可以看看user表的实现:
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
相关文章推荐
- Openstack Keystone 认证流程(六)--认证
- Openstack Keystone 认证流程(四)--Filter流水线
- Openstack Keystone 认证流程(二)--门
- juno版OpenStack部署流程--添加认证服务(keystone)(2)
- openstack身份认证与API请求流程
- Openstack Keystone 认证流程(五)--路由
- Openstack Keystone 认证流程(八)--总结
- Openstack组件部署 — Keystone功能介绍与认证实现流程
- OpenStack API 认证和 API 请求工作流程
- Openstack Keystone 认证流程(一)--Overview
- OpenStack点滴积累2--KeyStone的认证流程
- Openstack Keystone 认证流程(三)-WSGI
- openstack创建虚拟机keystone流程中文图
- openstack-wsgi的route中增加api流程详解(os-networks)增加
- 如何使用python3调用openstack keystone identity REST api接口获取X-AUTH-TOKEN
- Openstack组件部署 — Keystone Install & Create service entity and API endpoints
- Centos7手动部署Openstack Mitaka版安装配置--(三)安装keystone认证服务
- openstack keystone认证失败
- OpenStack 之Nova添加扩展API流程,附带资源的查找功能
- OpenStack 认证服务 KeyStone部署(三)