APIRouter执行了pecan_app的factory转到了v2_factory()方法
def APIRouter(**local_config):
return pecan_app.v2_factory(None, **local_config)
def _factory(global_config, **local_config):
return pecan_app.v2_factory(global_config, **local_config)
setattr(APIRouter, 'factory', _factory)
v2_factory里面就是pecan的相关内容了,hooks中定义了filter用于校验body合法性的,入口在 root.V2Controller(), 这里将v2controller 传给了pecan,创建了pecan的application
def v2_factory(global_config, **local_config):
# Processing Order:
# As request enters lower priority called before higher.
# Response 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
V2Contrller里面 通过lookup 实现了路由注册与转发, 该功能是pecan的一大特点
class V2Controller(object):
# Same data structure as neutron.api.versions.Versions for API backward
# compatibility
version_info = {
'id': 'v2.0',
'status': 'CURRENT'
}
_load_version_info(version_info)
# NOTE(blogan): Paste deploy handled the routing to the legacy extension
# controller. If the extensions filter is removed from the api-paste.ini
# then this controller will be routed to This means operators had
# the ability to turn off the extensions controller via tha api-paste but
# will not be able to turn it off with the pecan switch.
extensions = ext_ctrl.ExtensionsController()
@utils.expose(generic=True)
def index(self):
if not pecan.request.path_url.endswith('/'):
pecan.abort(404)
layout = []
for name, collection in _CORE_RESOURCES.items():
href = urllib.parse.urljoin(pecan.request.path_url, collection)
resource = {'name': name,
'collection': collection,
'links': [{'rel': 'self',
'href': href}]}
layout.append(resource)
return {'resources': layout}
@utils.when(index, method='HEAD')
@utils.when(index, method='POST')
@utils.when(index, method='PATCH')
@utils.when(index, method='PUT')
@utils.when(index, method='DELETE')
def not_supported(self):
pecan.abort(405)
@utils.expose()
def _lookup(self, collection, *remainder):
# if collection exists in the extension to service plugins map then
# we are assuming that collection is the service plugin and
# needs to be remapped.
# Example: https://neutron.endpoint/v2.0/lbaas/loadbalancers
if (remainder and
manager.NeutronManager.get_resources_for_path_prefix(
collection)):
collection = remainder[0]
remainder = remainder[1:]
controller = manager.NeutronManager.get_controller_for_resource(
collection)
if not controller:
LOG.warning("No controller found for: %s - returning response "
"code 404", collection)
pecan.abort(404)
# Store resource and collection names in pecan request context so that
# hooks can leverage them if necessary. The following code uses
# attributes from the controller instance to ensure names have been
# properly sanitized (eg: replacing dashes with underscores)
request.context['resource'] = controller.resource
request.context['collection'] = controller.collection
# NOTE(blogan): initialize a dict to store the ids of the items walked
# in the path for example: /networks/1234 would cause uri_identifiers
# to contain: {'network_id': '1234'}
# This is for backwards compatibility with legacy extensions that
# defined their own controllers and expected kwargs to be passed in
# with the uri_identifiers
request.context['uri_identifiers'] = {}
return controller, remainder
这里做了很多初始化的事情, 初始化neutronManager, 初始化extensionManager初始化, 以及将pecan中 router mapping中的collection与对应的controller 对应起来
def initialize_all():
manager.init()
ext_mgr = extensions.PluginAwareExtensionManager.get_instance()
ext_mgr.extend_resources("2.0", attributes.RESOURCES)
# At this stage we have a fully populated resource attribute map;
# build Pecan controllers and routes for all core resources
plugin = directory.get_plugin()
for resource, collection in RESOURCES.items():
resource_registry.register_resource_by_name(resource)
new_controller = res_ctrl.CollectionsController(collection, resource,
plugin=plugin)
manager.NeutronManager.set_controller_for_resource(
collection, new_controller)
manager.NeutronManager.set_plugin_for_resource(collection, plugin)
pecanized_resources = ext_mgr.get_pecan_resources()
for pec_res in pecanized_resources:
manager.NeutronManager.set_controller_for_resource(
pec_res.collection, pec_res.controller)
manager.NeutronManager.set_plugin_for_resource(
pec_res.collection, pec_res.plugin)
# Now build Pecan Controllers and routes for all extensions
resources = ext_mgr.get_resources()
# Extensions controller is already defined, we don't need it.
resources.pop(0)
for ext_res in resources:
path_prefix = ext_res.path_prefix.strip('/')
collection = ext_res.collection
# Retrieving the parent resource. It is expected the format of
# the parent resource to be:
# {'collection_name': 'name-of-collection',
# 'member_name': 'name-of-resource'}
# collection_name does not appear to be used in the legacy code
# inside the controller logic, so we can assume we do not need it.
parent = ext_res.parent or {}
parent_resource = parent.get('member_name')
collection_key = collection
if parent_resource:
collection_key = '/'.join([parent_resource, collection])
collection_actions = ext_res.collection_actions
member_actions = ext_res.member_actions
if manager.NeutronManager.get_controller_for_resource(collection_key):
# This is a collection that already has a pecan controller, we
# do not need to do anything else
continue
legacy_controller = getattr(ext_res.controller, 'controller',
ext_res.controller)
new_controller = None
if isinstance(legacy_controller, base.Controller):
resource = legacy_controller.resource
plugin = legacy_controller.plugin
attr_info = legacy_controller.attr_info
member_actions = legacy_controller.member_actions
pagination = legacy_controller.allow_pagination
sorting = legacy_controller.allow_sorting
# NOTE(blogan): legacy_controller and ext_res both can both have
# member_actions. the member_actions for ext_res are strictly for
# routing, while member_actions for legacy_controller are used for
# handling the request once the routing has found the controller.
# They're always the same so we will just use the ext_res
# member_action.
new_controller = res_ctrl.CollectionsController(
collection, resource, resource_info=attr_info,
parent_resource=parent_resource, member_actions=member_actions,
plugin=plugin, allow_pagination=pagination,
allow_sorting=sorting, collection_actions=collection_actions)
# new_controller.collection has replaced hyphens with underscores
manager.NeutronManager.set_plugin_for_resource(
new_controller.collection, plugin)
if path_prefix:
manager.NeutronManager.add_resource_for_path_prefix(
collection, path_prefix)
else:
new_controller = utils.ShimCollectionsController(
collection, None, legacy_controller,
collection_actions=collection_actions,
member_actions=member_actions,
action_status=ext_res.controller.action_status,
collection_methods=ext_res.collection_methods)
manager.NeutronManager.set_controller_for_resource(
collection_key, new_controller)
# 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()
neutronManager中 初始化函数中 _get_plugin_instance 完成了对core plugin ml2的加载 ,
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):
# Store instances of already loaded plugins to avoid instantiate same
# plugin more than once
self._loaded_plugins = {}
# 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)
neutron 中定义 core_plugin = ml2 , setup.cfg文件中的entry_points里定义neutron.core_plugins的实现类。
[entry_points]
neutron.core_plugins =
ml2 = neutron.plugins.ml2.plugin:Ml2Plugin
class Ml2Plugin(db_base_plugin_v2.NeutronDbPluginV2,
dvr_mac_db.DVRDbMixin,
external_net_db.External_net_db_mixin,
sg_db_rpc.SecurityGroupServerRpcMixin,
agentschedulers_db.AZDhcpAgentSchedulerDbMixin,
addr_pair_db.AllowedAddressPairsMixin,
vlantransparent_db.Vlantransparent_db_mixin,
extradhcpopt_db.ExtraDhcpOptMixin,
address_scope_db.AddressScopeDbMixin,
subnet_service_type_mixin.SubnetServiceTypeMixin,
config_item_db.ConfigItemDbMixin,
db_base_plugin_common.DbBasePluginCommon):
"""Implement the Neutron L2 abstractions using modules.
Ml2Plugin is a Neutron plugin based on separately extensible sets
of network types and mechanisms for connecting to networks of
those types. The network types and mechanisms are implemented as
drivers loaded via Python entry points. Networks can be made up of
multiple segments (not yet fully implemented).
"""
# This attribute specifies whether the plugin supports or not
# bulk/pagination/sorting operations. Name mangling is used in
# order to ensure it is qualified by class
__native_bulk_support = True
__native_pagination_support = True
__native_sorting_support = True
# This attribute specifies whether the plugin supports or not
# filter validations. Name mangling is used in
# order to ensure it is qualified by class
__filter_validation_support = True
# List of supported extensions
_supported_extension_aliases = [provider_net.ALIAS,
external_net.ALIAS, portbindings.ALIAS,
"quotas", "security-group",
rbac_sg_apidef.ALIAS,
agent_apidef.ALIAS,
dhcpagentscheduler.ALIAS,
subnet_dhcpagentscheduler.ALIAS,
multiprovidernet.ALIAS,
addr_apidef.ALIAS,
edo_ext.ALIAS, "subnet_allocation",
mtu_apidef.ALIAS,
mtuw_apidef.ALIAS,
vlan_apidef.ALIAS,
address_scope.ALIAS,
"config-item",
az_def.ALIAS,
network_availability_zone.ALIAS,
subnet_availability_zone.ALIAS,
availability_zone_filter.ALIAS,
default_subnetpools.ALIAS,
"subnet-service-types",
ip_substring_port_filtering.ALIAS,
security_groups_port_filtering.ALIAS,
empty_string_filtering.ALIAS,
filter_apidef.ALIAS,
port_mac_address_regenerate.ALIAS,
pbe_ext.ALIAS,
agent_resources_synced.ALIAS,
subnet_onboard_def.ALIAS]
# List of agent types for which all binding_failed ports should try to be
# rebound when agent revive
_rebind_on_revive_agent_types = [const.AGENT_TYPE_OVS]
@property
def supported_extension_aliases(self):
if not hasattr(self, '_aliases'):
aliases = self._supported_extension_aliases[:]
aliases += self.extension_manager.extension_aliases()
sg_rpc.disable_security_group_extension_by_config(aliases)
vlantransparent._disable_extension_by_config(aliases)
filter_validation._disable_extension_by_config(aliases)
self._aliases = aliases
return self._aliases
def __new__(cls, *args, **kwargs):
model_query.register_hook(
models_v2.Port,
"ml2_port_bindings",
query_hook=None,
filter_hook=None,
result_filters=_ml2_port_result_filter_hook)
return super(Ml2Plugin, cls).__new__(cls, *args, **kwargs)
@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__()
# TypeManager初始化
self.type_manager.initialize()
# ExtensionManager初始化
self.extension_manager.initialize()
# MechanismManager初始化
self.mechanism_manager.initialize()
# DHCP组件初始化
self._setup_dhcp()
# rpc_notifier初始化
self._start_rpc_notifiers()
self.add_agent_status_check_worker(self.agent_health_check)
self.add_workers(self.mechanism_manager.get_workers())
self._verify_service_plugins_requirements()
LOG.info("Modular L2 Plugin initialization complete")
这块ml2 plugin 加载过程中做的事情还挺多的,详细的可以再深入看下。