Table of Contents
0. systemctl status neutron-server:获取neutron-server 脚本入口... 2
1. /usr/bin/neutron-server:Main 函数入口... 2
2. cmd/eventlet/server/__init__.py:main函数实现... 3
3. server/wsgi_eventlet.py:wsgi_eventlet.eventlet_wsgi_server 函数实现... 3
4. service.py:serve_wsgi的实现... 4
4. service.py:NeutronApiService实现... 4
5. common/config.py:config.load_paste_app实现... 6
6. ../oslo_service/wsgi.py:wsgi.Loader.load_app的实现... 6
7. /usr/share/neutron/neutron-dist.conf:paste.deploy形式加载neutron的应用... 8
8. api/v2/router.py:APIRouter.factory实现... 10
9. pecan_wsgi/app.py:v2_factory实现... 10
10. manager.py:NeutronManager实现... 11
理解此加载过程需要预先具备以下几点知识:
* wsgi基本概念
* paste.deploy应用加载方式
* python pecan应用框架
0. systemctl status neutron-server:获取neutron-server 脚本入口
从systemctl status neutron-server 中获取neutron-server脚本入口
● neutron-server.service - OpenStack Neutron Server
Loaded: loaded (/usr/lib/systemd/system/neutron-server.service; enabled; vendor preset: disabled)
Active: active (running) since Mon 2021-02-15 21:59:30 PST; 4h 18min ago
Main PID: 19888 (neutron-server)
Tasks: 11
Memory: 1.0G
CGroup: /system.slice/neutron-server.service
├─19888 /usr/bin/python2 /usr/bin/neutron-server --config-file /usr/share/neutron/neutron-dist.conf --config-dir /usr/sh...
。。。
Hint: Some lines were ellipsized, use -l to show in full
1. /usr/bin/neutron-server:Main 函数入口
#!/usr/bin/python2
# PBR Generated from u'console_scripts'
import sys
from neutron.cmd.eventlet.server import main
if __name__ == "__main__":
sys.exit(main())
以下调试均在/usr/lib/python2.7/site-packages/neutron目录下。
2. cmd/eventlet/server/__init__.py:main函数实现
from neutron import server
from neutron.server import rpc_eventlet
from neutron.server import wsgi_eventlet
def main():
server.boot_server(wsgi_eventlet.eventlet_wsgi_server)
boot_server: 在做了两件事:
初始化配置文件, 此时暂时不展开。
执行传入的参数(函数)。
3. server/wsgi_eventlet.py:wsgi_eventlet.eventlet_wsgi_server 函数实现
def eventlet_wsgi_server():
neutron_api = service.serve_wsgi(service.NeutronApiService)
start_api_and_rpc_workers(neutron_api)
eventlet_wsgi_server函数中先是准备好wsgi服务相关要素,然后通过eventlet的方式启动。
此处我们主要探究wsgi服务准备部分:
neutron_api = service.serve_wsgi(service.NeutronApiService)
4. service.py:serve_wsgi的实现
def serve_wsgi(cls):
try:
service = cls.create()
service.start()
except Exception:
with excutils.save_and_reraise_exception():
LOG.exception('Unrecoverable error: please check log '
'for details.')
registry.notify(resources.PROCESS, events.BEFORE_SPAWN, service)
return service
调用传入的Service构造函数
启动服务:start
4. service.py:NeutronApiService实现
class NeutronApiService(WsgiService):
"""Class for neutron-api service."""
def __init__(self, app_name):
profiler.setup('neutron-server', cfg.CONF.host)
super(NeutronApiService, self).__init__(app_name)
@classmethod
def create(cls, app_name='neutron'):
# Setup logging early
config.setup_logging()
service = cls(app_name)
return service
NeutronApiService继承自WsgiService
class WsgiService(object):
"""Base class for WSGI based services.
For each api you define, you must also define these flags:
:<api>_listen: The address on which to listen
:<api>_listen_port: The port on which to listen
"""
def __init__(self, app_name):
self.app_name = app_name
self.wsgi_app = None
def start(self):
self.wsgi_app = _run_wsgi(self.app_name)
def wait(self):
self.wsgi_app.wait()
其中 _run_wsgi实现如下:
def _run_wsgi(app_name):
app = config.load_paste_app(app_name)
if not app:
LOG.error('No known API applications configured.')
return
return run_wsgi_app(app)
def run_wsgi_app(app):
server = wsgi.Server("Neutron")
server.start(app, cfg.CONF.bind_port, cfg.CONF.bind_host,
workers=_get_api_workers())
LOG.info("Neutron service started, listening on %(host)s:%(port)s",
{'host': cfg.CONF.bind_host, 'port': cfg.CONF.bind_port})
return server
config.load_paste_app(app_name) 通过paste.deploy的方式加载neutron服务。
run_wsgi_app启动一个一个web server。
5. common/config.py:config.load_paste_app实现
def load_paste_app(app_name):
"""Builds and returns a WSGI app from a paste config file.
:param app_name: Name of the application to load
"""
loader = wsgi.Loader(cfg.CONF)
app = loader.load_app(app_name)
return app
6. ../oslo_service/wsgi.py:wsgi.Loader.load_app的实现
class Loader(object):
"""Used to load WSGI applications from paste configurations."""
def __init__(self, conf):
"""Initialize the loader, and attempt to find the config.
:param conf: Application config
:returns: None
"""
conf.register_opts(_options.wsgi_opts)
self.config_path = None
config_path = conf.api_paste_config
if not os.path.isabs(config_path):
self.config_path = conf.find_file(config_path)
elif os.path.exists(config_path):
self.config_path = config_path
if not self.config_path:
raise ConfigNotFound(path=config_path)
def load_app(self, name):
"""Return the paste URLMap wrapped WSGI application.
:param name: Name of the application to load.
:returns: Paste URLMap object wrapping the requested application.
:raises: PasteAppNotFound
"""
try:
LOG.debug("Loading app %(name)s from %(path)s",
{'name': name, 'path': self.config_path})
return deploy.loadapp("config:%s" % self.config_path, name=name)
except LookupError:
LOG.exception("Couldn't lookup app: %s", name)
raise PasteAppNotFound(name=name, path=self.config_path)
在Loader初始化函数中,以传入的api_paste_config为配置路径加载相应应用实现。
回到最初systemctl status neutron-server --full命令的输出,
api_paste_config在配置文件列表中的/usr/share/neutron/neutron-dist.conf。
7. /usr/share/neutron/neutron-dist.conf:paste.deploy形式加载neutron的应用
配置项为:
api_paste_config = /usr/share/neutron/api-paste.ini
内容如下:
[composite:neutron]
use = egg:Paste#urlmap
/: neutronversions_composite
/v2.0: neutronapi_v2_0
[composite:neutronapi_v2_0]
use = call:neutron.auth:pipeline_factory
noauth = cors http_proxy_to_wsgi request_id catch_errors extensions neutronapiapp_v2_0
keystone = cors http_proxy_to_wsgi request_id catch_errors authtoken keystonecontext extensions neutronapiapp_v2_0
[composite:neutronversions_composite]
use = call:neutron.auth:pipeline_factory
noauth = cors http_proxy_to_wsgi neutronversions
keystone = cors http_proxy_to_wsgi neutronversions
[filter:request_id]
paste.filter_factory = oslo_middleware:RequestId.factory
[filter:catch_errors]
paste.filter_factory = oslo_middleware:CatchErrors.factory
[filter:cors]
paste.filter_factory = oslo_middleware.cors:filter_factory
oslo_config_project = neutron
[filter:http_proxy_to_wsgi]
paste.filter_factory = oslo_middleware.http_proxy_to_wsgi:HTTPProxyToWSGI.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
[filter:osprofiler]
paste.filter_factory = osprofiler.web:WsgiMiddleware.factory
这里我们需要理解 paste.deploy配置文件的具体含义:
* composite:多个子项的组合,向外提供新的应用。
* filter:增加新的逻辑处理。
* app:指明此处声明的是一个实际应用
可以看到neutronapi_v2_0应用是由多个子处理过程包装而成的。
提供了两种应用模式:noauth keystone,模式的选择配置为
/etc/neutron/neutron.conf:3:auth_strategy = keystone
这里我们仅关注:
[app:neutronapiapp_v2_0]
paste.app_factory = neutron.api.v2.router:APIRouter.factory
8. api/v2/router.py:APIRouter.factory实现
class APIRouter(base_wsgi.Router):
@classmethod
def factory(cls, global_config, **local_config):
if cfg.CONF.web_framework == 'pecan':
return pecan_app.v2_factory(global_config, **local_config)
return cls(**local_config)
factory函数声明格式是paste.deploy的标准形式。
* global_config中保存了来自上游的全局配置项CONF.xxx,
* local_config则保存了新增配置项,例如上边的"noauth", "keystone"都保存在local_config中。
函数的实现中,我们可以看到应用的框架使用pecan,这里需要了解python的pecan应用框架。
此框架所解决的问题依旧是如何将请求uri解析、路由到具体的应用。
9. pecan_wsgi/app.py:v2_factory实现
def v2_factory(global_config, **local_config):
# Processing Order:
# As request enters lower priority called before higher.
# Reponse from controller is passed from higher priority to lower.
app_hooks = [
hooks.UserFilterHook(), # priority 90
hooks.ContextHook(), # priority 95
hooks.ExceptionTranslationHook(), # priority 100
hooks.BodyValidationHook(), # priority 120
hooks.OwnershipValidationHook(), # priority 125
hooks.QuotaEnforcementHook(), # priority 130
hooks.NotifierHook(), # priority 135
hooks.QueryParametersHook(), # priority 139
hooks.PolicyHook(), # priority 140
]
app = pecan.make_app(root.V2Controller(),
debug=False,
force_canonical=False,
hooks=app_hooks,
guess_content_type_from_ext=True)
startup.initialize_all()
return app
此函数实现主要完成了neutron核心模块及api映射关系的加载过程,包括核心的neutron资源:
network subnet port 和 subnet pool.
api/v2/attributes.py:
CORE_RESOURCES = {net_def.RESOURCE_NAME: net_def.COLLECTION_NAME,
subnet_def.RESOURCE_NAME: subnet_def.COLLECTION_NAME,
subnetpool_def.RESOURCE_NAME: subnetpool_def.COLLECTION_NAME,
port_def.RESOURCE_NAME: port_def.COLLECTION_NAME}
关键api的映射是通过pecan controller router的的方式(相对于传统的wsgi路由对象的方式)。
V2Controller和startup.initialize_all中均会用到一个单例的NeutronManager。
V2Controller中并没有对NeutronManager实例化,该实例化发生在startup.initialize_all中。
在NeutronManager初始化函数中,存在加载extension plugins的逻辑。
10. manager.py:NeutronManager实现
@six.add_metaclass(profiler.TracedMeta)
class NeutronManager(object):
"""Neutron's Manager class.
Neutron's Manager class is responsible for parsing a config file and
instantiating the correct plugin that concretely implements
neutron_plugin_base class.
"""
# TODO(armax): use of the singleton pattern for this class is vestigial,
# and it is mainly relied on by the unit tests. It is safer to get rid
# of it once the entire codebase (neutron + subprojects) has switched
# entirely to using the plugins directory.
_instance = None
__trace_args__ = {"name": "rpc"}
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("Loading core plugin: %s", plugin_provider)
# NOTE(armax): keep hold of the actual plugin object
plugin = self._get_plugin_instance(CORE_PLUGINS_NAMESPACE,
plugin_provider)
directory.add_plugin(lib_const.CORE, plugin)
msg = validate_post_plugin_load()
if msg:
LOG.critical(msg)
raise Exception(msg)
# load services from the core plugin first
self._load_services_from_core_plugin(plugin)
self._load_service_plugins()
# Used by pecan WSGI
self.resource_plugin_mappings = {}
self.resource_controller_mappings = {}
self.path_prefix_resource_mappings = defaultdict(list)
def _load_service_plugins(self):
"""Loads service plugins.
Starts from the core plugin and checks if it supports
advanced services then loads classes provided in configuration.
"""
plugin_providers = cfg.CONF.service_plugins
plugin_providers.extend(self._get_default_service_plugins())
LOG.debug("Loading service plugins: %s", plugin_providers)
for provider in plugin_providers:
if provider == '':
continue
LOG.info("Loading Plugin: %s", provider)
plugin_inst = self._get_plugin_instance('neutron.service_plugins',
provider)
# only one implementation of svc_type allowed
# specifying more than one plugin
# for the same type is a fatal exception
# TODO(armax): simplify this by moving the conditional into the
# directory itself.
plugin_type = plugin_inst.get_plugin_type()
if directory.get_plugin(plugin_type):
raise ValueError(_("Multiple plugins for service "
"%s were configured") % plugin_type)
directory.add_plugin(plugin_type, plugin_inst)
# search for possible agent notifiers declared in service plugin
# (needed by agent management extension)
plugin = directory.get_plugin()
if (hasattr(plugin, 'agent_notifiers') and
hasattr(plugin_inst, 'agent_notifiers')):
plugin.agent_notifiers.update(plugin_inst.agent_notifiers)
LOG.debug("Successfully loaded %(type)s plugin. "
"Description: %(desc)s",
{"type": plugin_type,
"desc": plugin_inst.get_plugin_description()})
_load_service_plugins函数中,通过读取service_plugins得到extension plugin的列表(模块路径)。
plugin_providers = cfg.CONF.service_plugins
此配置在/etc/neutron/neutron.conf或者/etc/neutron/neutron_lbaas.conf中:
[DEFAULT]
service_plugins = neutron_lbaas.services.loadbalancer.plugin.LoadBalancerPluginv2,router,metering
然后根据service_provider得到plugin_inst
LOG.info("Loading Plugin: %s", provider)
plugin_inst = self._get_plugin_instance('neutron.service_plugins',
provider)
[service_providers]
service_provider = LOADBALANCERV2:F5Networks:neutron_lbaas.drivers.f5.driver_v2.F5LBaaSV2Driver:default
细节的追踪为:此处更适合单步跟踪调试
_get_plugin_instance
-> load_class_for_provider
-> utils.load_class_by_alias_or_classname
-> stevedore/driver.py: driver.DriverManager
-> stevedore/named.py: NamedExtensionManager
-> stevedore/extension.py: ExtensionManager._load_plugins
-> stevedore/extension.py: ExtensionManager._load_one_plugin
单步调试断点设置
(Pdb) n
> /usr/lib/python2.7/site-packages/oslo_service/wsgi.py(352)load_app()
-> {'name': name, 'path': self.config_path})
(Pdb) p self.config_path
'/usr/share/neutron/api-paste.ini'
(Pdb) l
347 :raises: PasteAppNotFound
348
349 """
350 try:
351 LOG.debug("Loading app %(name)s from %(path)s",
352 -> {'name': name, 'path': self.config_path})
353 return deploy.loadapp("config:%s" % self.config_path, name=name)
(Pdb) b /usr/lib/python2.7/site-packages/neutron/api/v2/router.py:70
66 class APIRouter(base_wsgi.Router):
67
68 @classmethod
69 def factory(cls, global_config, **local_config):
70 if cfg.CONF.web_framework == 'pecan':
71 return pecan_app.v2_factory(global_config, **local_config)
72 return cls(**local_config)
73
74 def __init__(self, **local_config):
75 mapper = routes_mapper.Mapper()
76 manager.init()
77 plugin = directory.get_plugin()
78 ext_mgr = extensions.PluginAwareExtensionManager.get_instance()
79 ext_mgr.extend_resources("2.0", attributes.RESOURCE_ATTRIBUTE_MAP)
80
81 col_kwargs = dict(collection_actions=COLLECTION_ACTIONS,
82 member_actions=MEMBER_ACTIONS)
83
(Pdb) b /usr/lib/python2.7/site-packages/neutron/pecan_wsgi/controllers/root.py:87
84
85 @utils.expose(generic=True)
86 def index(self):
87 if not pecan.request.path_url.endswith('/'):
88 pecan.abort(404)
89
90 layout = []
91 for name, collection in attributes.CORE_RESOURCES.items():
92 href = urlparse.urljoin(pecan.request.path_url, collection)
93 resource = {'name': name,
94 'collection': collection,
95 'links': [{'rel': 'self',
96 'href': href}]}
97 layout.append(resource)
98 return {'resources': layout}
(Pdb) b /usr/lib/python2.7/site-packages/neutron/pecan_wsgi/controllers/root.py:114
108 @utils.expose()
109 def _lookup(self, collection, *remainder):
110 # if collection exists in the extension to service plugins map then
111 # we are assuming that collection is the service plugin and
112 # needs to be remapped.
113 # Example: https://neutron.endpoint/v2.0/lbaas/loadbalancers
114 if (remainder and
115 manager.NeutronManager.get_resources_for_path_prefix(
116 collection)):
117 collection = remainder[0]
118 remainder = remainder[1:]
119 controller = manager.NeutronManager.get_controller_for_resource(
120 collection)
121 if not controller:
122 LOG.warning("No controller found for: %s - returning response "
123 "code 404", collection)
124 pecan.abort(404)
125 # Store resource and collection names in pecan request context so that
126 # hooks can leverage them if necessary. The following code uses
127 # attributes from the controller instance to ensure names have been
128 # properly sanitized (eg: replacing dashes with underscores)
129 request.context['resource'] = controller.resource
130 request.context['collection'] = controller.collection
131 # NOTE(blogan): initialize a dict to store the ids of the items walked
132 # in the path for example: /networks/1234 would cause uri_identifiers
133 # to contain: {'network_id': '1234'}
134 # This is for backwards compatibility with legacy extensions that
135 # defined their own controllers and expected kwargs to be passed in
136 # with the uri_identifiers
137 request.context['uri_identifiers'] = {}
138 return controller, remainder
b /usr/lib/python2.7/site-packages/neutron/manager.py:116
99 @six.add_metaclass(profiler.TracedMeta)
100 class NeutronManager(object):
101 """Neutron's Manager class.
102
103 Neutron's Manager class is responsible for parsing a config file and
104 instantiating the correct plugin that concretely implements
105 neutron_plugin_base class.
106 """
107 # TODO(armax): use of the singleton pattern for this class is vestigial,
108 # and it is mainly relied on by the unit tests. It is safer to get rid
109 # of it once the entire codebase (neutron + subprojects) has switched
110 # entirely to using the plugins directory.
111 _instance = None
112 __trace_args__ = {"name": "rpc"}
113
114 def __init__(self, options=None, config_file=None):
115 # If no options have been provided, create an empty dict
116 if not options:
117 options = {}
118
119 msg = validate_pre_plugin_load()
120 if msg:
121 LOG.critical(msg)
122 raise Exception(msg)
123
124 # NOTE(jkoelker) Testing for the subclass with the __subclasshook__
125 # breaks tach monitoring. It has been removed
126 # intentionally to allow v2 plugins to be monitored
127 # for performance metrics.
128 plugin_provider = cfg.CONF.core_plugin
-> 'neutron.plugins.ml2.plugin.Ml2Plugin'
129 LOG.info("Loading core plugin: %s", plugin_provider)
130 # NOTE(armax): keep hold of the actual plugin object
131 plugin = self._get_plugin_instance(CORE_PLUGINS_NAMESPACE,
132 plugin_provider)
-> <neutron.plugins.ml2.plugin.Ml2Plugin object at 0x7fda262eb9d0>
133 directory.add_plugin(lib_const.CORE, plugin)
-> (Pdb) p directory.get_plugins()
{'CORE': <weakproxy at 0x7fda252560a8 to Ml2Plugin at 0x7fda262eb9d0>}
134 msg = validate_post_plugin_load()
135 if msg:
136 LOG.critical(msg)
137 raise Exception(msg)
138
139 # load services from the core plugin first
140 self._load_services_from_core_plugin(plugin)
141 self._load_service_plugins()
-> backtrace:
-> return pecan_app.v2_factory(global_config, **local_config)
/usr/lib/python2.7/site-packages/neutron/pecan_wsgi/app.py(47)v2_factory()
-> startup.initialize_all()
/usr/lib/python2.7/site-packages/neutron/pecan_wsgi/startup.py(39)initialize_all()
-> manager.init()
/usr/lib/python2.7/site-packages/neutron/manager.py(296)init()
-> NeutronManager.get_instance()
/usr/lib/python2.7/site-packages/neutron/manager.py(247)get_instance()
-> cls._create_instance()
/usr/lib/python2.7/site-packages/oslo_concurrency/lockutils.py(271)inner()
-> return f(*args, **kwargs)
/usr/lib/python2.7/site-packages/neutron/manager.py(233)_create_instance()
-> cls._instance = cls()
/usr/lib/python2.7/site-packages/neutron/manager.py(141)__init__()
-> self._load_service_plugins()
/usr/lib/python2.7/site-packages/neutron/manager.py(203)_load_service_plugins()
-> provider)
/usr/lib/python2.7/site-packages/neutron/manager.py(165)_get_plugin_instance()
-> plugin_class = self.load_class_for_provider(namespace, plugin_provider)
/usr/lib/python2.7/site-packages/neutron/manager.py(159)load_class_for_provider()
-> plugin_provider)
/usr/lib/python2.7/site-packages/neutron/common/utils.py(330)load_class_by_alias_or_classname()
-> class_to_load = importutils.import_class(name)
> /usr/lib/python2.7/site-packages/oslo_utils/importutils.py(24)import_class()
-> def import_class(import_str):
-> <class 'neutron_lbaas.services.loadbalancer.plugin.LoadBalancerPluginv2'>
-> (Pdb) p directory.get_plugins()
{'CORE': <weakproxy at 0x7fda25235f70 to Ml2Plugin at 0x7fda262eb9d0>,
'LOADBALANCERV2': <weakproxy at 0x7fda25235f18 to LoadBalancerPluginv2 at 0x7fda250369d0>}
142 # Used by pecan WSGI
143 self.resource_plugin_mappings = {}
144 self.resource_controller_mappings = {}
145 self.path_prefix_resource_mappings = defaultdict(list)