分享

Neutron分析(1)——neutron-server启动过程分析

pig2 发表于 2014-10-15 11:19:09 [显示全部楼层] 回帖奖励 阅读模式 关闭右栏 3 61792

导读
本文启动分析,可以简单了解
Neutron-Server启动包含哪些步骤?





neutron-server启动过程分析
1. /etc/init.d/neutron-server
  1. DAEMON=/usr/bin/neutron-server
  2. DAEMON_ARGS="--log-file=$LOGFILE"
  3. DAEMON_DIR=/var/run
  4. ...
  5. case $1 in
  6.     start)
  7.         test "$ENABLED" = "true" || exit 0
  8.         log_daemon_msg "Starting neutron server" "neutron-server"
  9.         start-stop-daemon -Sbmv --pidfile $PIDFILE --chdir $DAEMON_DIR --exec $DAEMON -- $DAEMON_ARGS
  10.         log_end_msg $?
  11.         ;;
  12.         ...
  13. esac
复制代码





2. /usr/bin/neutron-server
  1. import sys
  2. from neutron.server import main
  3. if __name__ == "__main__":
  4.     sys.exit(main())
复制代码




3. neutron.server.main
  1. ef main():
  2.     # the configuration will be read into the cfg.CONF global data structure
  3.     config.init(sys.argv[1:])
  4.     if not cfg.CONF.config_file:
  5.         sys.exit(_("ERROR: Unable to find configuration file via the default"
  6.                    " search paths (~/.neutron/, ~/, /etc/neutron/, /etc/) and"
  7.                    " the '--config-file' option!"))
  8.     try:
  9.         pool = eventlet.GreenPool()
  10.         # 以协程方式启动Restful API
  11.         neutron_api = service.serve_wsgi(service.NeutronApiService)
  12.         api_thread = pool.spawn(neutron_api.wait)
  13.         # 启动RPC API
  14.         try:
  15.             neutron_rpc = service.serve_rpc()
  16.         except NotImplementedError:
  17.             LOG.info(_("RPC was already started in parent process by plugin."))
  18.         else:
  19.             rpc_thread = pool.spawn(neutron_rpc.wait)
  20.             # api and rpc should die together.  When one dies, kill the other.
  21.             rpc_thread.link(lambda gt: api_thread.kill())
  22.             api_thread.link(lambda gt: rpc_thread.kill())
  23.         pool.waitall()
  24.     except KeyboardInterrupt:
  25.         pass
  26.     except RuntimeError as e:
  27.         sys.exit(_("ERROR: %s") % e)
复制代码


4. 先看neutron.service.serve_rpc()
neutron.service.serve_rpc()最重要的工作就是启动各个插件的RpcWorker
  1. plugin = manager.NeutronManager.get_plugin()
  2. try:
  3.         rpc = RpcWorker(plugin)
  4.         if cfg.CONF.rpc_workers < 1:
  5.             rpc.start()
  6.             return rpc
  7.         else:
  8.             launcher = common_service.ProcessLauncher(wait_interval=1.0)
  9.             launcher.launch_service(rpc, workers=cfg.CONF.rpc_workers)
  10.             return launcher
复制代码


而RpcWorker最重要的工作是调用plugin的start_rpc_listeners来监听消息队列:
  1. def start(self):
  2.         # We may have just forked from parent process.  A quick disposal of the
  3.         # existing sql connections avoids producing errors later when they are
  4.         # discovered to be broken.
  5.         session.get_engine().pool.dispose()
  6.         self._servers = self._plugin.start_rpc_listeners()
复制代码


5. 再来看Rest API部分
  1. service.serve_wsgi(service.NeutronApiService)
  2. def serve_wsgi(cls):
  3.     try:
  4.         service = cls.create()
  5.         service.start()
  6.     except Exception:
  7.         with excutils.save_and_reraise_exception():
  8.             LOG.exception(_('Unrecoverable error: please check log '
  9.                             'for details.'))
  10.     return service
复制代码


service.start()即为self.wsgi_app = _run_wsgi(self.app_name),而该函数最重要的工作是从api-paste.ini中加载app并启动
  1. def _run_wsgi(app_name):
  2.     app = config.load_paste_app(app_name)
  3.     if not app:
  4.         LOG.error(_('No known API applications configured.'))
  5.         return
  6.     server = wsgi.Server("Neutron")
  7.     server.start(app, cfg.CONF.bind_port, cfg.CONF.bind_host,
  8.                  workers=cfg.CONF.api_workers)
  9.     # Dump all option values here after all options are parsed
  10.     cfg.CONF.log_opt_values(LOG, std_logging.DEBUG)
  11.     LOG.info(_("Neutron service started, listening on %(host)s:%(port)s"),
  12.              {'host': cfg.CONF.bind_host,
  13.               'port': cfg.CONF.bind_port})
  14.     return server
复制代码


6. api-paste.ini
  1. [composite:neutron]
  2. use = egg:Paste#urlmap
  3. /: neutronversions
  4. /v2.0: neutronapi_v2_0
  5. [composite:neutronapi_v2_0]
  6. use = call:neutron.auth:pipeline_factory
  7. noauth = request_id catch_errors extensions neutronapiapp_v2_0
  8. keystone = request_id catch_errors authtoken keystonecontext extensions neutronapiapp_v2_0
  9. [filter:request_id]
  10. paste.filter_factory = neutron.openstack.common.middleware.request_id:RequestIdMiddleware.factory
  11. [filter:catch_errors]
  12. paste.filter_factory = neutron.openstack.common.middleware.catch_errors:CatchErrorsMiddleware.factory
  13. [filter:keystonecontext]
  14. paste.filter_factory = neutron.auth:NeutronKeystoneContext.factory
  15. [filter:authtoken]
  16. paste.filter_factory = keystoneclient.middleware.auth_token:filter_factory
  17. [filter:extensions]
  18. paste.filter_factory = neutron.api.extensions:plugin_aware_extension_middleware_factory
  19. [app:neutronversions]
  20. paste.app_factory = neutron.api.versions:Versions.factory
  21. [app:neutronapiapp_v2_0]
  22. paste.app_factory = neutron.api.v2.router:APIRouter.factory
复制代码



实例化neutron/api/v2/router.py中的APIRouter


  1. class APIRouter(wsgi.Router):
  2. # 一个工厂类方法
  3. @classmethod
  4. def factory(cls, global_config, **local_config):
  5. return cls(**local_config)
  6. # 真正调用的实例化方法
  7. def __init__(self, **local_config):
  8. ...
  9. #获取NeutornManage的core_plugin,这个定义在/etc/neutron/neutron.conf,比如我的是
  10. #core_plugin = neutron.plugins.openvswitch.ovs_neutron_plugin.OVSNeutronPluginV2
  11. plugin = manager.NeutronManager.get_plugin()
  12. #扫描特定路径下的extensions
  13. ext_mgr = extensions.PluginAwareExtensionManager.get_instance()
  14. ...
  15. #定义的局部方法
  16. def _map_resource(collection, resource, params, parent=None):
  17. ...
  18. controller = base.create_resource(
  19. collection, resource, plugin, params, allow_bulk=allow_bulk,
  20. parent=parent, allow_pagination=allow_pagination,
  21. allow_sorting=allow_sorting)
  22. ...
  23. # 将这些resource加进router中
  24. return mapper.collection(collection, resource, **mapper_kwargs)
  25. # 遍历 {'network': 'networks', 'subnet': 'subnets','port': 'ports'}
  26. # 添加controller
  27. for resource in RESOURCES:
  28. _map_resource(RESOURCES[resource], resource,
  29. attributes.RESOURCE_ATTRIBUTE_MAP.get(
  30. RESOURCES[resource], dict()))
  31. for resource in SUB_RESOURCES:
  32. ...
复制代码


#其实操作和上面一个差不多,
由这个可以看出,添加的controller类型主要分为三类:(其实只要你在neutron目录下grep一下,看哪里调用了create_resource方法即可)
  1. OVSNeutronPluginV2
  2. extensions/*.py
  3. plugins/*.py
复制代码


针对前两途径加载resource的类,下面慢慢进行描述。至于第三种,则是在各个不同的plugin内部额外实现的,不是必须的。
顺便简单的提一下,在neutron/api/extensions.py下的get_instance方法,这里其实也是和nova一样,是遍历目录下的py文件,来增加extension的
  1. ...
  2. @classmethod
  3. def get_instance(cls):
  4. if cls._instance is None:
  5. cls._instance = cls(get_extensions_path(),
  6. ... NeutronManager.get_service_plugins())
复制代码



Resource:OVSNeutronPluginV2的实现
看了代码的你肯定知道,OVSNeutronPluginV2这个类,作为core_plugin继承了好多的的类
  1. class OVSNeutronPluginV2(db_base_plugin_v2.NeutronDbPluginV2,
  2. external_net_db.External_net_db_mixin,
  3. extraroute_db.ExtraRoute_db_mixin,
  4. l3_gwmode_db.L3_NAT_db_mixin,
  5. sg_db_rpc.SecurityGroupServerRpcMixin,
  6. l3_agentschedulers_db.L3AgentSchedulerDbMixin,
  7. agentschedulers_db.DhcpAgentSchedulerDbMixin,
  8. portbindings_db.PortBindingMixin,
  9. extradhcpopt_db.ExtraDhcpOptMixin,
  10. addr_pair_db.AllowedAddressPairsMixin):
复制代码


OVSNeutronPluginV2基本上没有什么自己的method,全靠它的"爹们"了。
随便抓两个来看下,比如NeutronDbPluginV2,他的method有get_port,create_network之类的,还有L3_NAT_db_mixin的create_router等。反正与db的操作,OVSNeutronPluginV2是不会管的,都在它的父类那边处理。
再看看OVSNeutronPluginV2继承的这些父类们:
  1. #NeutronDbPluginV2继承自NeutronPluginBaseV2
  2. class NeutronDbPluginV2(neutron_plugin_base_v2.NeutronPluginBaseV2,
  3. CommonDbMixin):
  4. class NeutronPluginBaseV2(...) :
  5. @abstractmethod
  6. def create_subnet(self, context, subnet):
  7. @abstractmethod
  8. def update_subnet(self, context, id, subnet):
  9. @abstractmethod
  10. def get_subnet(self, context, id, fields=None):
  11. @abstractmethod
  12. def get_subnets(self, context, filters=None, fields=None,
复制代码


其类图如下:(仅展示部分)


基本上可以说有一个接口类(如图中的NeutronPluginBaseV2),定义了抽象方法,然后一个具体的db类来实现(如NeutronDbPluginV2,这里是采用SQLAlchemy来完成db模型的)
plugin_aware_extension_middleware_factory
在请求进入APIRouter之前,会先经过RequestIdMiddleware(请求header中添加 openstack.request_id)、CatchErrorsMiddleware(错误处理)、keystone权限验证以及 plugin_aware_extension_middleware_factory等几个filter的处理,前三个filter比较直 观,plugin_aware_extension_middleware_factory初始化了Extension目录下的Resource:

  1. class ExtensionMiddleware(wsgi.Middleware):
  2.     """Extensions middleware for WSGI."""
  3.     def __init__(self, application,
  4.                  ext_mgr=None):
  5.         self.ext_mgr = (ext_mgr
  6.                         or ExtensionManager(get_extensions_path()))
  7.         mapper = routes.Mapper()
  8.         # extended resources ext_mgr.get_resources()其实在内部会调用每个extensions目录下的extension类的get_resources方法
  9.         for resource in self.ext_mgr.get_resources():
  10.             path_prefix = resource.path_prefix
  11.             if resource.parent:
  12.                 path_prefix = (resource.path_prefix +
  13.                                "/%s/{%s_id}" %
  14.                                (resource.parent["collection_name"],
  15.                                 resource.parent["member_name"]))
  16.             LOG.debug(_('Extended resource: %s'),
  17.                       resource.collection)
  18.             for action, method in resource.collection_actions.iteritems():
  19.                 conditions = dict(method=[method])
  20.                 path = "/%s/%s" % (resource.collection, action)
  21.                 with mapper.submapper(controller=resource.controller,
  22.                                       action=action,
  23.                                       path_prefix=path_prefix,
  24.                                       conditions=conditions) as submap:
  25.                     submap.connect(path)
  26.                     submap.connect("%s.:(format)" % path)
  27.             mapper.resource(resource.collection, resource.collection,
  28.                             controller=resource.controller,
  29.                             member=resource.member_actions,
  30.                             parent_resource=resource.parent,
  31.                             path_prefix=path_prefix)
  32.         # extended actions
  33.         action_controllers = self._action_ext_controllers(application,
  34.                                                           self.ext_mgr, mapper)
  35.         for action in self.ext_mgr.get_actions():
  36.             LOG.debug(_('Extended action: %s'), action.action_name)
  37.             controller = action_controllers[action.collection]
  38.             controller.add_action(action.action_name, action.handler)
  39.         # extended requests
  40.         req_controllers = self._request_ext_controllers(application,
  41.                                                         self.ext_mgr, mapper)
  42.         for request_ext in self.ext_mgr.get_request_extensions():
  43.             LOG.debug(_('Extended request: %s'), request_ext.key)
  44.             controller = req_controllers[request_ext.key]
  45.             controller.add_handler(request_ext.handler)
  46.         self._router = routes.middleware.RoutesMiddleware(self._dispatch,
  47.                                                           mapper)
  48.         super(ExtensionMiddleware, self).__init__(application)
复制代码

比如在extensions下的securitygroup.py中的get_resources方法,看这个代码就知道其中可以处理security_group和security_group_rule两类请求了:

  1. @classmethod
  2.     def get_resources(cls):
  3.         """Returns Ext Resources."""
  4.         my_plurals = [(key, key[:-1]) for key in RESOURCE_ATTRIBUTE_MAP.keys()]
  5.         attr.PLURALS.update(dict(my_plurals))
  6.         exts = []
  7.         plugin = manager.NeutronManager.get_plugin()
  8.         for resource_name in ['security_group', 'security_group_rule']:
  9.             collection_name = resource_name.replace('_', '-') + "s"
  10.             params = RESOURCE_ATTRIBUTE_MAP.get(resource_name + "s", dict())
  11.             quota.QUOTAS.register_resource_by_name(resource_name)
  12.             controller = base.create_resource(collection_name,
  13.                                               resource_name,
  14.                                               plugin, params, allow_bulk=True,
  15.                                               allow_pagination=True,
  16.                                               allow_sorting=True)
  17.             ex = extensions.ResourceExtension(collection_name,
  18.                                               controller,
  19.                                               attr_map=params)
  20.             exts.append(ex)
  21.         return exts
复制代码

如此,Neutron-Server就已经基本上启动了,无外乎就是加载配置,router各种resource,然后就等待请求了。其中router哪些resource完全是由配置文件来决定的。 当然,在启动的过程中也会初始化db,这也就是为何在安装neutron的时候无需像nova,glance等要执行db sync的原因了。





已有(3)人评论

跳转到指定楼层
sheldon 发表于 2015-1-23 16:59:04
其中router哪些resource完全是由配置文件来决定的? 请问是哪个配置文件的哪个参数来决定的?
回复

使用道具 举报

cucsea 发表于 2015-9-11 16:42:26
好复杂,看不太懂
回复

使用道具 举报

dasheyuan 发表于 2016-1-9 15:57:53
https://github.com/openstack/neutron
master分支/etc文件加下没有init.d文件了,修改到哪里去了?
stable/liberty分支中还有。
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

关闭

推荐上一条 /2 下一条