openstack neutron网络模块分析(二)--- APIRouter

neutron APIRouter分析

环境:ocata版本,centos7
这篇文章主要分析的几个点:
一、 APIRouter究竟是如何将client端发来的http请求路由到指定的资源controller
二、 Plugin和extension的创建过程

APIRouter

代码目录:/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'}

后面还有很多东西,这里就不多分析了,个人可以跟踪代码一步一步的的分析。

service_plugins extension的加载

这里只创建了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启动plugin与extension过程

这个主要是结合日志查看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了

你可能感兴趣的:(openstack,neutron)