openstack Rocky系列之keystone:(二)keystone中API注册
主要说一下initialize_application中的application_factory
1 def loadapp(): 2 app = application.application_factory(name) 3 return app
源码如下
1 @fail_gracefully 2 def application_factory(name='public'): 3 if name not in ('admin', 'public'): 4 raise RuntimeError('Application name (for base_url lookup) must be ' 5 'either `admin` or `public`.') 6 7 # NOTE(morgan): The Flask App actually dispatches nothing until we migrate 8 # some routers to Flask-Blueprints, it is simply a placeholder. 9 app = flask.Flask(name) 10 app.after_request(_add_vary_x_auth_token_header) 11 12 # NOTE(morgan): Configure the Flask Environment for our needs. 13 app.config.update( 14 # We want to bubble up Flask Exceptions (for now) 15 PROPAGATE_EXCEPTIONS=True) 16 17 # TODO(morgan): Convert Subsystems over to Flask-Native, for now, we simply 18 # dispatch to another "application" [e.g "keystone"] 19 # NOTE(morgan): as each router is converted to flask-native blueprint, 20 # remove from this list. WARNING ORDER MATTERS; ordered dict used to 21 # ensure sane ordering of the routers in the legacy-dispatch model. 22 dispatch_map = collections.OrderedDict() 23 24 # Load in Healthcheck and map it to /healthcheck 25 hc_app = healthcheck.Healthcheck.app_factory( 26 {}, oslo_config_project='keystone') 27 dispatch_map['/healthcheck'] = hc_app 28 29 # More legacy code to instantiate all the magic for the dispatchers. 30 # The move to blueprints (FLASK) will allow this to be eliminated. 31 _routers = [] 32 sub_routers = [] 33 mapper = routes.Mapper() 34 for api_routers in ALL_API_ROUTERS: 35 moved_found = [pfx for 36 pfx in getattr(api_routers, '_path_prefixes', []) 37 if pfx in _MOVED_API_PREFIXES] 38 if moved_found: 39 raise RuntimeError('An API Router is trying to register path ' 40 'prefix(s) `%(pfx)s` that is handled by the ' 41 'native Flask app. Keystone cannot ' 42 'start.' % 43 {'pfx': ', '.join([p for p in moved_found])}) 44 45 routers_instance = api_routers.Routers() 46 _routers.append(routers_instance) 47 routers_instance.append_v3_routers(mapper, sub_routers) 48 # TODO(morgan): Remove "API version registration". For now this is kept 49 # for ease of conversion (minimal changes) 50 keystone.api.discovery.register_version('v3') 51 52 # NOTE(morgan): We add in all the keystone.api blueprints here, this 53 # replaces (as they are implemented) the legacy dispatcher work. 54 for api in keystone.api.__apis__: 55 for api_bp in api.APIs: 56 api_bp.instantiate_and_register_to_app(app) 57 58 # Build and construct the dispatching for the Legacy dispatching model 59 sub_routers.append(_ComposibleRouterStub(_routers)) 60 legacy_dispatcher = keystone_wsgi.ComposingRouter(mapper, sub_routers) 61 62 for pfx in itertools.chain(*[rtr.Routers._path_prefixes for 63 rtr in ALL_API_ROUTERS]): 64 import pdb;pdb.set_trace() 65 dispatch_map['/v3/%s' % pfx] = legacy_dispatcher 66 app.wsgi_app = KeystoneDispatcherMiddleware( 67 app.wsgi_app, 68 dispatch_map) 69 return app
fail_gracefully是个装饰器,主要是为了防止函数报错,抓一下exception,并记录报错信息,其中又使用了一个functools.wraps的装饰器,防止fail_gracefully对被装饰的函数造成影响
主要看27行dispatch_map = collections.OrderedDict(),这里创建了一个dispatch_map的变量,这个变量用于记录所有的路由分发
33行创建了一个router.Mapper()的对象mapper
1 for api_routers in ALL_API_ROUTERS: 2 moved_found = [pfx for 3 pfx in getattr(api_routers, '_path_prefixes', []) 4 if pfx in _MOVED_API_PREFIXES] 5 if moved_found: 6 raise RuntimeError('An API Router is trying to register path ' 7 'prefix(s) `%(pfx)s` that is handled by the ' 8 'native Flask app. Keystone cannot ' 9 'start.' % 10 {'pfx': ', '.join([p for p in moved_found])}) 11 12 routers_instance = api_routers.Routers() 13 _routers.append(routers_instance) 14 routers_instance.append_v3_routers(mapper, sub_routers)
ALL_API_ROUTER是一个包含所有处理路由的类的列表,这个循环中主要是过滤掉所有的被移除的API,api_routes.Routers()继承wsgi.RoutersBase,并通过append_v3_routers将自己的路由信息记录到mapper中,其中sub_routers是记录顶层路由下的路由
sub_routers.append(_ComposibleRouterStub(_routers))这行代码将_router整合到sub_routers中
legacy_dispatcher = keystone_wsgi.ComposingRouter(mapper, sub_routers)这行代码最终调用的源代码如下
1 class Router(wsgi.ComposableRouter): 2 def __init__(self, controller, collection_key, key, 3 resource_descriptions=None, 4 is_entity_implemented=True, 5 method_template=None): 6 self.controller = controller 7 self.key = key 8 self.collection_key = collection_key 9 self._resource_descriptions = resource_descriptions 10 self._is_entity_implemented = is_entity_implemented 11 self.method_template = method_template or '%s' 12 13 def add_routes(self, mapper): 14 collection_path = '/%(collection_key)s' % { 15 'collection_key': self.collection_key} 16 entity_path = '/%(collection_key)s/{%(key)s_id}' % { 17 'collection_key': self.collection_key, 18 'key': self.key} 19 20 mapper.connect( 21 collection_path, 22 controller=self.controller, 23 action=self.method_template % 'create_%s' % self.key, 24 conditions=dict(method=['POST'])) 25 mapper.connect( 26 collection_path, 27 controller=self.controller, 28 action=self.method_template % 'list_%s' % self.collection_key, 29 conditions=dict(method=['GET', 'HEAD'])) 30 mapper.connect( 31 entity_path, 32 controller=self.controller, 33 action=self.method_template % 'get_%s' % self.key, 34 conditions=dict(method=['GET', 'HEAD'])) 35 mapper.connect( 36 entity_path, 37 controller=self.controller, 38 action=self.method_template % 'update_%s' % self.key, 39 conditions=dict(method=['PATCH'])) 40 mapper.connect( 41 entity_path, 42 controller=self.controller, 43 action=self.method_template % 'delete_%s' % self.key, 44 conditions=dict(method=['DELETE'])) 45 46 # Add the collection resource and entity resource to the resource 47 # descriptions. 48 49 collection_rel = json_home.build_v3_resource_relation( 50 self.collection_key) 51 rel_data = {'href': collection_path, } 52 self._resource_descriptions.append((collection_rel, rel_data)) 53 json_home.JsonHomeResources.append_resource(collection_rel, rel_data) 54 55 if self._is_entity_implemented: 56 entity_rel = json_home.build_v3_resource_relation(self.key) 57 id_str = '%s_id' % self.key 58 id_param_rel = json_home.build_v3_parameter_relation(id_str) 59 entity_rel_data = { 60 'href-template': entity_path, 61 'href-vars': { 62 id_str: id_param_rel, 63 }, 64 } 65 self._resource_descriptions.append((entity_rel, entity_rel_data)) 66 json_home.JsonHomeResources.append_resource( 67 entity_rel, entity_rel_data)
通过add_router为route添加mapper
1 for pfx in itertools.chain(*[rtr.Routers._path_prefixes for 2 rtr in ALL_API_ROUTERS]): 3 dispatch_map['/v3/%s' % pfx] = legacy_dispatcher
这里主要添加dispatch_map中的前缀和处理代码的映射关系,涉及到itertools.chain()用法的小技巧
最后一行的KeystoneDispatcherMiddleware类负责API的分发,同样是通过__call__()这个魔法函数