环境:ocata版本,centos7
这篇文章主要分析的几个点:
一、 APIRouter究竟是如何将client端发来的http请求路由到指定的资源controller
二、 Plugin和extension的创建过程
代码目录:/neutron/api/v2/router.py
class APIRouter(base_wsgi.Router):
@classmethod
def factory(cls, global_config, **local_config):
return cls(**local_config)
这里是以工厂方法去构造一个APIRouter对象
def __init__(self, **local_config):
mapper = routes_mapper.Mapper()
manager.init()
plugin = directory.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
controller = base.create_resource(
collection, resource, plugin, params, allow_bulk=allow_bulk,
parent=parent, allow_pagination=True,
allow_sorting=True)
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)
上面的就是 APIRouter的核心代码了 ,下面详细分析:
**先看 **
mapper = routes_mapper.Mapper()
先声明一个routes Mapper,(Mapper的代码能从安装openstack的环境下的/usr/lib/python2.7/site-packages/routes下),
def connect(self, *args, **kargs):
"""Create and connect a new Route to the Mapper.
Usage:
.. code-block:: python
m = Mapper()
m.connect(':controller/:action/:id')
m.connect('date/:year/:month/:day', controller="blog", action="view")
m.connect('archives/:page', controller="blog", action="by_page",
requirements = { 'page':'\d{1,2}' })
m.connect('category_list', 'archives/category/:section', controller='blog', action='category',
section='home', type='list')
m.connect('home', '', controller='blog', action='view', section='home')
"""
这里是connect的说明代码,
而这个routes Mapper的作用就是构造URL和对应controller的映射,根据不同的URL请求路由给不同的controller处理
接着
manager.init()
其实就是运行/neutron/manager下的init,也就是下面这段代码
def init():
"""Call to load the plugins (core+services) machinery."""
# TODO(armax): use is_loaded() when available
if not directory.get_plugins():
NeutronManager.get_instance()
初始化一个manager的单例
def init():
"""Call to load the plugins (core+services) machinery."""
# TODO(armax): use is_loaded() when available
if not directory.get_plugins():
NeutronManager.get_instance()
这里的NeutronManager.get_instance()需要重点分析(这里把一些注释去掉)
@six.add_metaclass(profiler.TracedMeta)
class NeutronManager(object):
_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)
plugin_provider = cfg.CONF.core_plugin
LOG.info(_LI("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)
这能只分析self._load_service_plugins()这个,其它的请自行追踪代码去分析
部分代码
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())
这里要说的是除了核心的ml2 plugins, 其它plugins究竟是怎么添加和加载的
上面两句就是所有其它plugins的内容了,
# 配置文件neutron.conf 定义的service_plugins
# 此时环境配置为 service_plugins = router,neutron.services.metering.metering_plugin.MeteringPlugin
plugin_providers = cfg.CONF.service_plugins
下面这个是加载代码写死的service_plguins
plugin_providers.extend(self._get_default_service_plugins())
def _get_default_service_plugins(self):
"""Get default service plugins to be loaded."""
return constants.DEFAULT_SERVICE_PLUGINS.keys()
跳到neutron.plugins.common.contants.py查看DEFAULT_SERVICE_PLUGINS内容
# Maps default service plugins entry points to their extension aliases
DEFAULT_SERVICE_PLUGINS = {
'auto_allocate': 'auto-allocated-topology',
'tag': 'tag',
'timestamp': 'timestamp',
'network_ip_availability': 'network-ip-availability',
'flavors': 'flavors',
'revisions': 'revisions',
}
上面加起来的就是所有的service_plugins了,所以,要加载自定义组件有两种方式,将写好的plugins以配置文件方式加入,或者加在DEFAULT_SERVICE_PLUGINS下。
所有的service都会加到directory._plugin的字段里
插件加载
plugin = directory.get_plugin()
这里就会首先会加载core plugin 然后再到service plugin
extension加载
ext_mgr = extensions.PluginAwareExtensionManager.get_instance()
这里会加载所有plugin下的所有extension
ml2 plugin支持的extension
目录:/neutron/plugins/ml2/plugin.py
# List of supported extensions
_supported_extension_aliases = ["provider", "external-net", "binding",
"quotas", "security-group", "agent",
"dhcp_agent_scheduler",
"multi-provider", "allowed-address-pairs",
"extra_dhcp_opt", "subnet_allocation",
"net-mtu", "vlan-transparent",
"address-scope",
"availability_zone",
"network_availability_zone",
"default-subnetpools",
"subnet-service-types"
]
所有service plugin的extension
目录:/neutron/service/所有文件夹的plugin文件下的 _supported_extension_aliases
重点
def _map_resource(collection, resource, params, parent=None):
allow_bulk = cfg.CONF.allow_bulk
controller = base.create_resource(
collection, resource, plugin, params, allow_bulk=allow_bulk,
parent=parent, allow_pagination=True,
allow_sorting=True)
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)
这里就是将资源和URL映射起来了,构造成controller了,具体是怎么构建的呢,就要去看/neutron/api/v2/base.py下的create_resource代码了,这里只做简单分析
def create_resource(collection, resource, plugin, params, allow_bulk=False,
member_actions=None, parent=None, allow_pagination=False,
allow_sorting=False):
controller = Controller(plugin, collection, resource, params, allow_bulk,
member_actions=member_actions, parent=parent,
allow_pagination=allow_pagination,
allow_sorting=allow_sorting)
return wsgi_resource.Resource(controller, FAULT_MAP)
重点看controller这个类:
class Controller(object):
LIST = 'list'
SHOW = 'show'
CREATE = 'create'
UPDATE = 'update'
DELETE = 'delete'
看到这里是不是有种熟悉的味道,没错,这五个就是我们要对某个资源的操作了,比如子网,create_subnet
所以
下面这段代码就好理解了:
for resource in RESOURCES:
_map_resource(RESOURCES[resource], resource,
attributes.RESOURCE_ATTRIBUTE_MAP.get(
RESOURCES[resource], dict()))
resource_registry.register_resource_by_name(resource)
就是创建这些controller了:
RESOURCES = {'network': 'networks',
'subnet': 'subnets',
'subnetpool': 'subnetpools',
'port': 'ports'}
后面还有很多东西,这里就不多分析了,个人可以跟踪代码一步一步的的分析。
这里只创建了RESOURCES的资源,对于其它extension并没有去map
其它的service_plugins是在
neutron.api.extensions:plugin_aware_extension_middleware_factory
这里处理的
至于这里是怎么得来的需要去了解 paste.ini文件分析这里
必现要搞懂paste.ini的工作原理,才能理解下面的。
def plugin_aware_extension_middleware_factory(global_config, **local_config):
"""Paste factory."""
def _factory(app):
# PluginAwareExtensionManager
ext_mgr = PluginAwareExtensionManager.get_instance()
return ExtensionMiddleware(app, ext_mgr=ext_mgr)
return _factory
这里上面再次使用到PluginAwareExtensionManager.get_instance()
由上面的分析得到是所有的service_plugins了
然后分析ExtensionMiddleware这个类,app是APIRouter返回的app,下面是部分代码
class ExtensionMiddleware(base.ConfigurableMiddleware):
"""Extensions middleware for WSGI."""
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 +
"/%s/{%s_id}" %
(resource.parent["collection_name"],
resource.parent["member_name"]))
LOG.debug('Extended resource: %s',
resource.collection)
for action, method in six.iteritems(resource.collection_actions):
conditions = dict(method=[method])
path = "/%s/%s" % (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 + "_format",
"%s.:(format)" % path)
重点是这句for resource in self.ext_mgr.get_resources()
这个get_resource()是类的方法,具体需要根据前面的ext_mgr了。
由上面分析可以知道配置文件的
service_plugins = router,neutron.services.metering.metering_plugin.MeteringPlugin
就可以得到MeteringPlugin是在ext_mgr里的,所以可以以这个为分析
class MeteringPlugin(metering_db.MeteringDbMixin):
"""Implementation of the Neutron Metering Service Plugin."""
supported_extension_aliases = ["metering"]
path_prefix = "/metering"
这里supported_extension_aliases只有metering
所以直接查看文件neutron.extensions.metering.py
class Metering(extensions.ExtensionDescriptor):
@classmethod
def get_resources(cls):
"""Returns Ext Resources."""
plural_mappings = resource_helper.build_plural_mappings(
{}, RESOURCE_ATTRIBUTE_MAP)
# PCM: Metering sets pagination and sorting to True. Do we have cfg
# entries for these so can be read? Else, must pass in.
return resource_helper.build_resource_info(plural_mappings,
RESOURCE_ATTRIBUTE_MAP,
constants.METERING,
translate_name=True,
allow_bulk=True)
这个类的get_resources方法就是我们要找的,但是这样一看怎么加载的还不是很明朗,需要再追踪
def build_resource_info(plural_mappings, resource_map, which_service,
action_map=None, register_quota=False,
translate_name=False, allow_bulk=False):
resources = []
if not which_service:
which_service = constants.CORE
if action_map is None:
action_map = {}
plugin = directory.get_plugin(which_service)
path_prefix = getattr(plugin, "path_prefix", "")
LOG.debug('Service %(service)s assigned prefix: %(prefix)s',
{'service': which_service, 'prefix': path_prefix})
for collection_name in resource_map:
resource_name = plural_mappings[collection_name]
params = resource_map.get(collection_name, {})
if translate_name:
collection_name = collection_name.replace('_', '-')
if register_quota:
resource_registry.register_resource_by_name(resource_name)
member_actions = action_map.get(resource_name, {})
controller = base.create_resource(
collection_name, resource_name, plugin, params,
member_actions=member_actions,
allow_bulk=allow_bulk,
allow_pagination=True,
allow_sorting=True)
resource = extensions.ResourceExtension(
collection_name,
controller,
path_prefix=path_prefix,
member_actions=member_actions,
attr_map=params)
resources.append(resource)
return resources
看到resource_helper.build_resource_info函数的实现应该就明白了如何加载了吧,和APIRouter加载方式是一样的。
这里最次强调core plugin和service的创建是由
entry_points.txt
以及/neutron/plugins/common/constants.py文件共同决定
# Maps extension alias to service type that
# can be implemented by the core plugin.
EXT_TO_SERVICE_MAPPING = {
'dummy': DUMMY,
'lbaas': LOADBALANCER,
'lbaasv2': LOADBALANCERV2,
'fwaas': FIREWALL,
'aas': VPN,
'metering': METERING,
'router': constants.L3,
'qos': QOS,
}
# Maps default service plugins entry points to their extension aliases
DEFAULT_SERVICE_PLUGINS = {
'auto_allocate': 'auto-allocated-topology',
'tag': 'tag',
'timestamp': 'timestamp',
'network_ip_availability': 'network-ip-availability',
'flavors': 'flavors',
'revisions': 'revisions',
}
上面两个地方就是决定了加载哪些plugin了
这个主要是结合日志查看neutron-server启动时的顺序是如何的
这里可以看到ML2 plugin的创建过程
对应的代码就是在/neutron/plugins/,l2/plugin.py
@resource_registry.tracked_resources(
network=models_v2.Network,
port=models_v2.Port,
subnet=models_v2.Subnet,
subnetpool=models_v2.SubnetPool,
security_group=sg_models.SecurityGroup,
security_group_rule=sg_models.SecurityGroupRule)
def __init__(self):
# First load drivers, then initialize DB, then initialize drivers
self.type_manager = managers.TypeManager()
self.extension_manager = managers.ExtensionManager()
self.mechanism_manager = managers.MechanismManager()
super(Ml2Plugin, self).__init__()
这里初始化了type_manager,mechanism_manager这两个管理器,分别用来管理type和mechanism.其中不同的网络拓扑类型对应着Type Driver,而网络实现机制对应着Mechanism Driver。这两个管理器都是通过stevedor来管理的,这样就可以向查找标准库一样管理Type,Mechanism Driver了。
而这里就是一些plugin的加载与对应的extension的创建了
最后这里就是wsgi生产worker了