设为首页 收藏本站
查看: 1366|回复: 0

[经验分享] how to read openstack code: loading process

[复制链接]
发表于 2017-12-5 11:32:01 | 显示全部楼层 |阅读模式
  之前我们了解了neutron的结构,plugin 和 extension等信息。这一章我们看一下neutron如何加载这些plugin和extension。也就是neutron的启动过程。本文涉及的代码较多,而且调用过程复杂... 所以你手头最好有一份liberty版本的neutron代码,参考来看
  回顾一下neutron的paste配置文件
  

[composite:neutron]  
use = egg:Paste#urlmap
  
/: neutronversions
  
/v2.0: neutronapi_v2_0
  

  
[composite:neutronapi_v2_0]
  
use = call:neutron.auth:pipeline_factory
  
noauth = request_id catch_errors extensions neutronapiapp_v2_0
  
keystone = request_id catch_errors authtoken keystonecontext extensions neutronapiapp_v2_0
  

  
[filter:request_id]
  
paste.filter_factory = oslo_middleware:RequestId.factory
  

  
[filter:catch_errors]
  
paste.filter_factory = oslo_middleware:CatchErrors.factory
  

  
[filter:keystonecontext]
  
paste.filter_factory = neutron.auth:NeutronKeystoneContext.factory
  

  
[filter:authtoken]
  
paste.filter_factory = keystonemiddleware.auth_token:filter_factory
  

  
[filter:extensions]
  
paste.filter_factory = neutron.api.extensions:plugin_aware_extension_middleware_factory
  

  
[app:neutronversions]
  
paste.app_factory = neutron.api.versions:Versions.factory
  

  
[app:neutronapiapp_v2_0]
  
paste.app_factory = neutron.api.v2.router:APIRouter.factory
  

  最重要的是extensions neutronapiapp_v2_0这两个组件。前一个是wsgi middleware, 后一个是wsgi app。 我们先看后一个,因为按照加载顺序,也必然是它先加载

neutronapiapp_v2_0
  从配置文件不难定义到其代码在 neutron.api.v2.router.py 的 APIRouter类的factory函数。这是设计模式中的工厂函数。虽然python可能不太适用JAVA设计模式,但。。可能这些程序员之前是java程序员。该factory函数的作用就是构建APIRouter类的一个实例。我们看一下构造函数
  

    def __init__(self, **local_config):  mapper = routes_mapper.Mapper()
  plugin = manager.NeutronManager.get_plugin()
  ext_mgr = extensions.PluginAwareExtensionManager.get_instance()
  ext_mgr.extend_resources("2.0", attributes.RESOURCE_ATTRIBUTE_MAP)
  

  col_kwargs = dict(collection_actions=COLLECTION_ACTIONS,
  member_actions=MEMBER_ACTIONS)
  

  def _map_resource(collection, resource, params, parent=None):
  allow_bulk = cfg.CONF.allow_bulk
  allow_pagination = cfg.CONF.allow_pagination
  allow_sorting = cfg.CONF.allow_sorting
  controller = base.create_resource(
  collection, resource, plugin, params, allow_bulk=allow_bulk,
  parent=parent, allow_pagination=allow_pagination,
  allow_sorting=allow_sorting)
  path_prefix = None
  if parent:
  path_prefix = "/%s/{%s_id}/%s" % (parent['collection_name'],
  parent['member_name'],
  collection)
  mapper_kwargs = dict(controller=controller,
  requirements=REQUIREMENTS,
  path_prefix=path_prefix,
  **col_kwargs)
  return mapper.collection(collection, resource,
  **mapper_kwargs)
  

  mapper.connect('index', '/', controller=Index(RESOURCES))
  for resource in RESOURCES:
  _map_resource(RESOURCES[resource], resource,
  attributes.RESOURCE_ATTRIBUTE_MAP.get(
  RESOURCES[resource], dict()))
  resource_registry.register_resource_by_name(resource)
  

  for resource in SUB_RESOURCES:
  _map_resource(SUB_RESOURCES[resource]['collection_name'], resource,
  attributes.RESOURCE_ATTRIBUTE_MAP.get(
  SUB_RESOURCES[resource]['collection_name'],
  dict()),
  SUB_RESOURCES[resource]['parent'])
  

  # Certain policy checks require that the extensions are loaded
  # and the RESOURCE_ATTRIBUTE_MAP populated before they can be
  # properly initialized. This can only be claimed with certainty
  # once this point in the code has been reached. In the event
  # that the policies have been initialized before this point,
  # calling reset will cause the next policy check to
  # re-initialize with all of the required data in place.
  policy.reset()
  super(APIRouter, self).__init__(mapper)
  

  这部分代码调用若要展开详细说明会非常多,所以我们只挑重点部分,分为几个小节大概说明。
  mapper是routes的mapper。它的后面会把url 和 逻辑处理模块映射起来。这里先创建,后面会增加映射。

NeutronManager
  plugin = manager.NeutronManager.get_plugin() 比较关键。 所以我们这里单独用一个小节来说明。这里最关键的就是neutron manager这个类
  

"""Neutron's Manager>

  
Neutron's Manager>  
instantiating the correct plugin that concretely implements

  
neutron_plugin_base>  
The caller should make sure that NeutronManager is a singleton.
  
"""
  

  上面是NeutronManager的注释,有几点内容注意:
  

singleton: 就是说在使用该类时要确保它是单实例的  
instantiating plugin: 就是说该类是用来加载plugin的
  

  这里说加载所有正确实施了neutron_plugin_base接口并且在配置文件中配置了的plugin,有点不太准确。因为core plugin 和service plugin的接口其实是不一样的。不过,我们只要知道它加载plugin的就可以了。这里plugin 包括core和service
  单实例是确保这个class只实例化一次,这样plugin也就只会被加载一次。 下面我们看一下这个class 如何加载plugin。其构造函数代码如下
  

def __init__(self, options=None, config_file=None):  # If no options have been provided, create an empty dict
  if not options:
  options = {}
  

  msg = validate_pre_plugin_load()
  if msg:
  LOG.critical(msg)
  raise Exception(msg)
  

  # NOTE(jkoelker) Testing for the subclass with the __subclasshook__
  #                breaks tach monitoring. It has been removed
  #                intentionally to allow v2 plugins to be monitored
  #                for performance metrics.
  plugin_provider = cfg.CONF.core_plugin
  LOG.info(_LI("Loading core plugin: %s"), plugin_provider)
  self.plugin = self._get_plugin_instance(CORE_PLUGINS_NAMESPACE,
  plugin_provider)
  msg = validate_post_plugin_load()
  if msg:
  LOG.critical(msg)
  raise Exception(msg)
  

  # core plugin as a part of plugin collection simplifies
  # checking extensions
  # TODO(enikanorov): make core plugin the same as
  # the rest of service plugins
  self.service_plugins = {constants.CORE: self.plugin}
  self._load_service_plugins()
  # Used by pecan WSGI
  self.resource_plugin_mappings = {}
  self.resource_controller_mappings = {}
  

  plugin_provider = cfg.CONF.core_plugin就是配置文件/etc/neutron/neutron.conf中
  

[default]  
core_plugin =
  

  的内容。通常这里是ml2。不管是什么,这里是core plugin注册在neutron.core_plugins下的entry point名字。如果你继续追踪self._get_plugin_instance的代码,就会发现是我们之前讲过的stevedore调用。
  
下面比较关键的地方是这段注释和代码
  

    # core plugin as a part of plugin collection simplifies  # checking extensions
  # TODO(enikanorov): make core plugin the same as
  # the rest of service plugins
  self.service_plugins = {constants.CORE: self.plugin}
  self._load_service_plugins()
  

  从注释中可知core plugin将会被当作service plugin处理。这是个趋势.
  这个类的service_plugins属性是一个字典,字典的key是service type而value是service_plugin实例。 这里把core plugin当作一种service plugin加了进去。下面的self._load_service_plugins会继续填充这个字典,加载配置文件中配置的所有service plugin。
  假设我们配置文件中配置了
  

core_plugin = ml2  
service_plugin = router, zoo
  

  那么现在这个字典的值就是
  

{‘CORE’: <ML2 instance>, 'ROUTER': <ROUTER instance>, 'ZOO': <ZOO instance>}  

  ok,说了那么多。我们现在总结一个重点就是,NeutronManager负责加载所有的配置在配置文件中的core plugin和service plugin。我们继续看

ExtensionManager
  接着看ext_mgr = extensions.PluginAwareExtensionManager.get_instance()这行代码。这里最关键的自然是PluginAwareExtensionManager这个类。但为什么这一小节不是说它呢?因为它继承自ExtensionManager。
  

extensions.PluginAwareExtensionManager.get_instance()  

  这里的get_instance猜也可以猜到是获取PluginAwareExtensionManager的实例的。不过还是有一点玄机的:
  

@classmethod  
def get_instance(cls):
  if cls._instance is None:
  service_plugins = manager.NeutronManager.get_service_plugins()
  cls._instance = cls(get_extensions_path(service_plugins),
  service_plugins)
  return cls._instance
  

  最初状态下,需要先获取service_plugins这个字典。然后调用get_extensions_path获取系统中存放所有extension的路径。再然后才是运行PluginAwareExtension的构造函数
  

def __init__(self, path, plugins):  self.plugins = plugins
  super(PluginAwareExtensionManager, self).__init__(path)
  self.check_if_plugin_extensions_loaded()
  

  而这个构造函数就三行。大部分的工作都是第二行,也就是我们这一小节的主角ExtensionManager的构造函数。 OK。。。我们这一小节的主题终于开始了。
  

def _load_all_extensions_from_path(self, path):  # Sorting the extension list makes the order in which they
  # are loaded predictable across a cluster of load-balanced
  # Neutron Servers
  for f in sorted(os.listdir(path)):
  try:
  LOG.debug('Loading extension file: %s', f)
  mod_name, file_ext = os.path.splitext(os.path.split(f)[-1])
  ext_path = os.path.join(path, f)
  if file_ext.lower() == '.py' and not mod_name.startswith('_'):
  mod = imp.load_source(mod_name, ext_path)
  ext_name = mod_name[0].upper() + mod_name[1:]
  new_ext_class = getattr(mod, ext_name, None)
  if not new_ext_class:
  LOG.warn(_LW('Did not find expected name '
  '&quot;%(ext_name)s&quot; in %(file)s'),
  {'ext_name': ext_name,
  'file': ext_path})
  continue
  new_ext = new_ext_class()
  self.add_extension(new_ext)
  except Exception as exception:
  LOG.warn(_LW(&quot;Extension file %(f)s wasn't loaded due to &quot;
  &quot;%(exception)s&quot;),
  {'f': f, 'exception': exception})
  

  根据构造函数追踪,可以追踪到上面这个类,读代码可知,该类会加载系统中extension path,默认是neutron/extensions下所有的extension。加载的时候有几个条件



  • extension的名字必须是file名和class同名,不过class名字的首字母要大写
  • extension的文件名不能开头是_
  这些也正符合我们之前说的写extension要符合的几个要求。
  OK, 总结重点,ExtensionManager加载了所有的extension,存在它的extensions属性中。该属性是一个字典,key是extension的别名,value是extension的实例

PluginAwareExtensionManager
  上面我们已经说了PluignAwareExtensionManager的构造函数是下面这样。
  

def __init__(self, path, plugins):  self.plugins = plugins
  super(PluginAwareExtensionManager, self).__init__(path)
  self.check_if_plugin_extensions_loaded()
  

  目前为止,我们知道了NeutronManager加载所有plugin, ExtensionManager加载extension,那么这个类是干嘛的呢?从名字看它说Plugin aware Extension,其实正是这样。
  之前的blog中我们说extension有三种



  • resource extension 新增资源
  • action extension 新增action
  • request extension 扩充request
  很多plugin独自不能实现所需的api功能,这些plugin就需要extension。plugin有一个supported_extension_alias记录了它需要的extension的列表。这个类就会根据这个列表检查plugin所需的extension是否加载了。
  看这个构造函数,第一行获取所有的plugin,第二行获取所有的extension,第三行不言而喻,就是根据plugin来check extension是否加载

ExtensionManager.extend_resource
  我们看看下面的代码。
  

    ext_mgr.extend_resources(&quot;2.0&quot;, attributes.RESOURCE_ATTRIBUTE_MAP)  

  目前为止我们加载了所有的plugin,所有的extension,并且确保了plugin所需的extension都加载了。这一行代码其实是跟我们之前说的request extension相关
  我们知道request extension的作用是为request添加一些参数。这些参数落到RESTful的资源中其实就是资源的属性,既然是资源的属性,那么就需要在RESOURCE_ATTRIBUTE_MAP定义的资源中有相应的内容。比如
  最初的RESOURCE_ATTRIBUTE_MAP定义可能如下:
  

RESOURCE_ATTRIBUTE_MAP = {  NETWORKS: {
  'id': {'allow_post': False, 'allow_put': False,
  'validate': {'type:uuid': None},
  'is_visible': True,
  'primary_key': True},
  'name': {'allow_post': True, 'allow_put': True,
  'validate': {'type:string': NAME_MAX_LEN},
  'default': '', 'is_visible': True},
  'subnets': {'allow_post': False, 'allow_put': False,
  'default': [],
  'is_visible': True},
  'admin_state_up': {'allow_post': True, 'allow_put': True,
  'default': True,
  'convert_to': convert_to_boolean,
  'is_visible': True},
  'status': {'allow_post': False, 'allow_put': False,
  'is_visible': True},
  'tenant_id': {'allow_post': True, 'allow_put': False,
  'validate': {'type:string': TENANT_ID_MAX_LEN},
  'required_by_policy': True,
  'is_visible': True},
  SHARED: {'allow_post': True,
  'allow_put': True,
  'default': False,
  'convert_to': convert_to_boolean,
  'is_visible': True,
  'required_by_policy': True,
  'enforce_policy': True},
  },
  

  这里并没有 some_attr这个属性,所以你在创建网络的时候如果带了这个属性就会报错。而resource extension会update这个字典,添加这个属性。
  ext_mgr是ExtensionManager的实例,它拥有所有的extension,因此通过遍历自己的extension,找到所有的request extension并用其update RESOURCE_ATTRIBUTE_MAP,可以把API需要的属性注册到系统中。换句话说,到了这里,所有的resource extension 的最主要任务完成了。

_map_resource
  接下来最重要的是这个函数。该函数的作用是把resource的URL 和 controller映射起来。 Resource是作为参数传入的,重点是controller。它是由
  

        controller = base.create_resource(  collection, resource, plugin, params, allow_bulk=allow_bulk,
  parent=parent, allow_pagination=allow_pagination,
  allow_sorting=allow_sorting)
  

  这段话创建的。这个controller最后会包装成一个wsgi的资源。这一过程中 POST/GET 等HTTP method 被翻译成create 和 get等action,而controller由通过这些action去调用plugin中对应的方法,进而完成api操作。
  重要的是这里处理的只有核心资源
  到这里APIRouter的主要内容就分析完了。接下来看 extension middleware

ExtensionMiddleware
  从paste的文件首先找到这个工厂函数
  

def plugin_aware_extension_middleware_factory(global_config, **local_config):  &quot;&quot;&quot;Paste factory.&quot;&quot;&quot;
  def _factory(app):
  ext_mgr = PluginAwareExtensionManager.get_instance()
  return ExtensionMiddleware(app, ext_mgr=ext_mgr)
  return _factory
  

  它返回了一个PluginAwareExtensionManager的实例。这个实例我们之前说过,没什么特殊的。重要的是它拥有所有的配置的plugin和extension。重点看ExtensionMiddleware。其构造函数如下
  

def __init__(self, application, ext_mgr=None):  self.ext_mgr = (ext_mgr or ExtensionManager(get_extensions_path()))
  mapper = routes.Mapper()
  

  # extended resources
  for resource in self.ext_mgr.get_resources():
  path_prefix = resource.path_prefix
  if resource.parent:
  path_prefix = (resource.path_prefix +
  &quot;/%s/{%s_id}&quot; %
  (resource.parent[&quot;collection_name&quot;],
  resource.parent[&quot;member_name&quot;]))
  

  LOG.debug('Extended resource: %s',
  resource.collection)
  for action, method in six.iteritems(resource.collection_actions):
  conditions = dict(method=[method])
  path = &quot;/%s/%s&quot; % (resource.collection, action)
  with mapper.submapper(controller=resource.controller,
  action=action,
  path_prefix=path_prefix,
  conditions=conditions) as submap:
  submap.connect(path_prefix + path, path)
  submap.connect(path_prefix + path + &quot;_format&quot;,
  &quot;%s.:(format)&quot; % path)
  

  mapper.resource(resource.collection, resource.collection,
  controller=resource.controller,
  member=resource.member_actions,
  parent_resource=resource.parent,
  path_prefix=path_prefix)
  

  # extended actions
  action_controllers = self._action_ext_controllers(application,
  self.ext_mgr, mapper)
  for action in self.ext_mgr.get_actions():
  LOG.debug('Extended action: %s', action.action_name)
  controller = action_controllers[action.collection]
  controller.add_action(action.action_name, action.handler)
  

  # extended requests
  req_controllers = self._request_ext_controllers(application,
  self.ext_mgr, mapper)
  for request_ext in self.ext_mgr.get_request_extensions():
  LOG.debug('Extended request: %s', request_ext.key)
  controller = req_controllers[request_ext.key]
  controller.add_handler(request_ext.handler)
  

  self._router = routes.middleware.RoutesMiddleware(self._dispatch,
  mapper)
  super(ExtensionMiddleware, self).__init__(application)
  

  我们挑重点说。这里仅有的三个注释其实已经说明了这个middleware的重点。extension有三种,这里对应着这三条extension的处理
  首先是resource extension
  self.ext_mgr.get_resources是extension manager的函数,它遍历所有的resource extension,获取它们定义的所有resource,返回一个resource列表。接下来的内容就简单了,生成controller,映射url。
  其次是action extension
  同上。。
  接着request extension
  同上。。
  OK 上面就是neutron启动加载所有plugin和extension的过程

运维网声明 1、欢迎大家加入本站运维交流群:群②:261659950 群⑤:202807635 群⑦870801961 群⑧679858003
2、本站所有主题由该帖子作者发表,该帖子作者与运维网享有帖子相关版权
3、所有作品的著作权均归原作者享有,请您和我们一样尊重他人的著作权等合法权益。如果您对作品感到满意,请购买正版
4、禁止制作、复制、发布和传播具有反动、淫秽、色情、暴力、凶杀等内容的信息,一经发现立即删除。若您因此触犯法律,一切后果自负,我们对此不承担任何责任
5、所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其内容的准确性、可靠性、正当性、安全性、合法性等负责,亦不承担任何法律责任
6、所有作品仅供您个人学习、研究或欣赏,不得用于商业或者其他用途,否则,一切后果均由您自己承担,我们对此不承担任何法律责任
7、如涉及侵犯版权等问题,请您及时通知我们,我们将立即采取措施予以解决
8、联系人Email:admin@iyunv.com 网址:www.yunweiku.com

所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其承担任何法律责任,如涉及侵犯版权等问题,请您及时通知我们,我们将立即处理,联系人Email:kefu@iyunv.com,QQ:1061981298 本贴地址:https://www.yunweiku.com/thread-420765-1-1.html 上篇帖子: 深度探索 OpenStack Neutron:BGP(1) 【转载】 下篇帖子: Openstack 不同虚拟机类型的性能测试
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

扫码加入运维网微信交流群X

扫码加入运维网微信交流群

扫描二维码加入运维网微信交流群,最新一手资源尽在官方微信交流群!快快加入我们吧...

扫描微信二维码查看详情

客服E-mail:kefu@iyunv.com 客服QQ:1061981298


QQ群⑦:运维网交流群⑦ QQ群⑧:运维网交流群⑧ k8s群:运维网kubernetes交流群


提醒:禁止发布任何违反国家法律、法规的言论与图片等内容;本站内容均来自个人观点与网络等信息,非本站认同之观点.


本站大部分资源是网友从网上搜集分享而来,其版权均归原作者及其网站所有,我们尊重他人的合法权益,如有内容侵犯您的合法权益,请及时与我们联系进行核实删除!



合作伙伴: 青云cloud

快速回复 返回顶部 返回列表