neutron代码浅析(一)--启动流程


Neutron做为Openstack的网络组件,其内部所有功能均是以plugin形式实现的,其中极具代表性的plugin就是ml2和l3两个插件,下面我将从neutron启动的源码来简单介绍neutron加载组件和扩展的流程。


1 目录结构


2 启动

setup.cfg--entrypoint 配置

console_scripts =
    neutron-l3-agent = neutron.cmd.eventlet.agents.l3:main
    neutron-server = neutron.cmd.eventlet.server:main
neutron.core_plugins =
    ml2 = neutron.plugins.ml2.plugin:Ml2Plugin
neutron.service_plugins =
    router = neutron.services.l3_router.l3_router_plugin:L3RouterPlugin

<p>neutron没有api进程,它的所有的api操作都是有neutron-server来处理的,包括初始资源的载入,插件的载入,还有所有的数据库操作以及与各个agent之间的通信。</p>

# neutron/cmd/eventlet/server/__init__.py
def main():
    # 启动服务,并调用 _main_neutron_server
    server.boot_server(_main_neutron_server)
    
def _main_neutron_server():
    # neutron有两种api方式,一种是pecan一种是原来的legacy
    if cfg.CONF.web_framework == 'legacy':
        # 这里我们主要讲legacy 下面发析这一步
        wsgi_eventlet.eventlet_wsgi_server()
    else:
        wsgi_pecan.pecan_wsgi_server()

def main_rpc_eventlet():
    server.boot_server(rpc_eventlet.eventlet_rpc_server)
# neutron/server/wsgi_eventlet.py
def eventlet_wsgi_server():   
    # 程序从这里开始进入第二步 
    # 这是注册neutron-api 关键步骤,下面我们将首先分析这一过程
    neutron_api = service.serve_wsgi(service.NeutronApiService)
    # 这一步是利用eventlet的线程池启动api和rpc
    start_api_and_rpc_workers(neutron_api)
def start_api_and_rpc_workers(neutron_api):    
    pool = eventlet.GreenPool()
     # 这是关键的一步,下面我们进入neutron_api.wait进行分析
    api_thread = pool.spawn(neutron_api.wait)
    try: 
       # 注册rpc server
       neutron_rpc = service.serve_rpc()
    except NotImplementedError:
        LOG.info(_LI("RPC was already started in parent process by "                     "plugin."))
    else:
        #启动rpc
        rpc_thread = pool.spawn(neutron_rpc.wait)
        # 启动rpc plugin works
        plugin_workers = service.start_plugin_workers()
        for worker in plugin_workers:
            pool.spawn(worker.wait)
        # api and rpc should die together.  When one dies, kill the other.
        rpc_thread.link(lambda gt: api_thread.kill())
        api_thread.link(lambda gt: rpc_thread.kill())
    pool.waitall()

<p>分析api的注册和wait过程</p>

# neutron_api = service.serve_wsgi(service.NeutronApiService)
# neutron/service.py
class NeutronApiService(WsgiService):
    """Class for neutron-api service."""
    @classmethod
    def create(cls, app_name='neutron'): 
        # Setup logging early, supplying both the CLI options and the
        # configuration mapping from the config file
        # We only update the conf dict for the verbose and debug
        # flags. Everything else must be set up in the conf file...
        # Log the options used when starting if we're in debug mode...
        # 建立log日志
        config.setup_logging()
        # 这一步是关键,返回一个当前类的实例
        service = cls(app_name)
        return service
def serve_wsgi(cls):
    try:
        # cls = NeutronApiService
        service = cls.create()
        # 启动api server实例 下面我们分析这一步
        service.start()
    except Exception:
        with excutils.save_and_reraise_exception():
            LOG.exception(_LE('Unrecoverable error: please check log '                              'for details.'))
    return service

<p>分析service.start()</p>

# service.start()
# neutron/service.py
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):
        # 启动时  调用_run_wsgi加载app
        self.wsgi_app = _run_wsgi(self.app_name)
    def wait(self):
        self.wsgi_app.wait()

def _run_wsgi(app_name):
    # app_name 默认是neutron
    # 这一步比较关键,利用了develop paste
    # 先从配置文件api-paste.ini读取出要加载的app
    # 默认配置:  [app:neutronapiapp_v2_0]
    # paste.app_factory =  neutron.api.v2.router:APIRouter.factory
    # 所以这一步实际调用的是 neutron.api.v2.router ApiRouter实例的工厂方法factory生成一个router实例,
    # 这里的router不是neutron中路由的router,而是wsgi中路由的概念,即请求转发的规则
    # 从这一步开始,neutron从正式开始启动,并加载各种资源。
    app = config.load_paste_app(app_name)
    if not app:
        LOG.error(_LE('No known API applications configured.'))
        return
    return run_wsgi_app(app)

3、加载资源

<p>这一步骤主要是neutron加载配置的插件,并通过载入载入插件的资源和扩展</p>

# neutron.api.v2.router:APIRouter.factory
# neutron/api/v2/router.py
class APIRouter(base_wsgi.Router):

    @classmethod
    def factory(cls, global_config, **local_config):
        return cls(**local_config)

    def __init__(self, **local_config):
        # 调用routes包获取mapper对象,不多说,有需求的可以自己进去看看
        mapper = routes_mapper.Mapper()
        # 加载 插件 3.1会对其中具体步骤进行分析
        plugin = manager.NeutronManager.get_plugin()
        # 加载 扩展 3.2会对其中具体步骤进行分析
        ext_mgr = extensions.PluginAwareExtensionManager.get_instance()
        # 加载 扩展资源 3.3会对其中具体步骤进行分析
        ext_mgr.extend_resources("2.0", attributes.RESOURCE_ATTRIBUTE_MAP)
        # 生成方法字典,即请求中restful的请求对应的方法
        col_kwargs = dict(collection_actions=COLLECTION_ACTIONS,
                          member_actions=MEMBER_ACTIONS)

        def _map_resource(collection, resource, params, parent=None):
              """这里面主要是遍历生成资源图(router)的过程,不列代码了"""

3.1 载入插件机制分析

# manager.NeutronManager.get_plugin()
# neutron/manager.py
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.
    The caller should make sure that NeutronManager is a singleton.
    """
    _instance = None

    @classmethod
    def get_plugin(cls):
        # Return a weakref to minimize gc-preventing references.
        # weakref不多说,说另外两步,
        # 第一步获取manager实例cls.get_instance()
        # 第二步返回当前的self.plugin
        return weakref.proxy(cls.get_instance().plugin)

    @classmethoddef 
    def get_instance(cls):
        # double checked locking
        # 检测是否有实例
        if not cls.has_instance():
            # 没有则创建
            cls._create_instance()
        return cls._instance

    @classmethod
    def has_instance(cls):
        return cls._instance is not None

    @classmethod
    @utils.synchronized("manager")
    def _create_instance(cls):
        if not cls.has_instance():
            # 创建实例即调用__init__
            cls._instance = cls()

    def __init__(self, options=None, config_file=None):
        # If no options have been provided, create an empty dict
        if not options:
            options = {}
        # 验证是否配置了cor_plugin
        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(_LI("Loading core plugin: %s"), plugin_provider)
        # 加载核心插件
        self.plugin = self._get_plugin_instance(CORE_PLUGINS_NAMESPACE,
                                                plugin_provider)
        msg = validate_post_plugin_load()
        if msg:
            LOG.critical(msg)
            raise Exception(msg)

        # core plugin as a part of plugin collection simplifies
        # checking extensions
        # TODO(enikanorov): make core plugin the same as
        # the rest of service plugins
        # 加载service_plugins即服务插件,
        self.service_plugins = {constants.CORE: self.plugin}
        self._load_service_plugins()
        # Used by pecan WSGI 
        self.resource_plugin_mappings = {}
        self.resource_controller_mappings = {}

    def _get_plugin_instance(self, namespace, plugin_provider):
        # 利用stevdore加载插件,获取其实例
        plugin_class = self.load_class_for_provider(namespace, plugin_provider)
        return plugin_class()

    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.
        """
        # load services from the core plugin first
        self._load_services_from_core_plugin()

        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(_LI("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
            if plugin_inst.get_plugin_type() in self.service_plugins:
                raise ValueError(_("Multiple plugins for service "
                                   "%s were configured") %
                                 plugin_inst.get_plugin_type())

            self.service_plugins[plugin_inst.get_plugin_type()] = plugin_inst

            # search for possible agent notifiers declared in service plugin
            # (needed by agent management extension)
            if (hasattr(self.plugin, 'agent_notifiers') and
                    hasattr(plugin_inst, 'agent_notifiers')):
                self.plugin.agent_notifiers.update(plugin_inst.agent_notifiers)

            LOG.debug("Successfully loaded %(type)s plugin. "
                      "Description: %(desc)s",
                      {"type": plugin_inst.get_plugin_type(),
                       "desc": plugin_inst.get_plugin_description()})

3.2 载入扩展机制分析

# plugin = manager.NeutronManager.get_plugin()
# neutron/api/extensions.py
class PluginAwareExtensionManager(ExtensionManager):

    _instance = None

    @classmethod
    def get_instance(cls):
        if cls._instance is None:
            # 获取上一步加载的插件
            service_plugins = manager.NeutronManager.get_service_plugins()
            # 获取当前类的实例 并将扩展所在位置的路径注入
            cls._instance = cls(get_extensions_path(service_plugins),
                                service_plugins)
        return cls._instance
    def __init__(self, path, plugins):
        self.plugins = plugins
        # 调用父类初始化 注入路径 这个路径是neutron路径+所有service_plugin的路径
        # 父类的初始化方法进行了扩展的载入
        super(PluginAwareExtensionManager, self).__init__(path)
        # 检测插件扩展是否载入
        self.check_if_plugin_extensions_loaded()
    
    def check_if_plugin_extensions_loaded(self):
        """根据别名aliase判断哪些扩展没有载入并raise异常"""

    def _check_extension(self, extension):
        """Check if an extension is supported by any plugin."""
        extension_is_valid = super(PluginAwareExtensionManager,
                                   self)._check_extension(extension)
        return (extension_is_valid and
                self._plugins_support(extension) and
                self._plugins_implement_interface(extension))

class ExtensionManager(object):
    """Load extensions from the configured extension path.

    See tests/unit/extensions/foxinsocks.py for an
    example extension implementation.
    """

    def __init__(self, path):
        LOG.info(_LI('Initializing extension manager.'))
        # 子类注入路径
        # 这个路径是neutron路径+所有service_plugin的路径
        self.path = path
        self.extensions = {}
        # 载入所有扩展
        self._load_all_extensions()

    def _load_all_extensions(self):
        """Load extensions from the configured path.

        The extension name is constructed from the module_name. If your
        extension module is named widgets.py, the extension class within that
        module should be 'Widgets'.

        See tests/unit/extensions/foxinsocks.py for an example extension
        implementation.
        """

        for path in self.path.split(':'):
            if os.path.exists(path):
                # 这个路径是neutron路径+所有service_plugin的路径
                # 到这我们就基本明白如果你要给neutron写扩展需要放在什么位置了
                self._load_all_extensions_from_path(path)
            else:
                LOG.error(_LE("Extension path '%s' doesn't exist!"), path)

    def _load_all_extensions_from_path(self, path):
        # Sorting the extension list makes the order in which they
        # are loaded predictable across a cluster of load-balanced
        # Neutron Servers
        for f in sorted(os.listdir(path)):
            try:
                LOG.debug('Loading extension file: %s', f)
                mod_name, file_ext = os.path.splitext(os.path.split(f)[-1])
                ext_path = os.path.join(path, f)
                # 载入扩展文件 文件需要以.py结尾,并且不能是_开头
                if file_ext.lower() == '.py' and not mod_name.startswith('_'):
                    # 利用python的系统调用载入这个文件
                    # mod_name是文件名字,路径是文件路径
                    mod = imp.load_source(mod_name, ext_path)
                    # 生成扩展名,这一步很重要,扩展类的名称一定要是文件名的首字母大写
                    # 例如扩展l3.py 那么文件中的扩展类一定要是L3
                    ext_name = mod_name[0].upper() + mod_name[1:]
                    # 获取这个扩展文件中的扩展类
                    new_ext_class = getattr(mod, ext_name, None)
                    if not new_ext_class:
                        LOG.warning(_LW('Did not find expected name '
                                        '"%(ext_name)s" in %(file)s'),
                                    {'ext_name': ext_name,
                                     'file': ext_path})
                        continue
                    # 实例化这个扩展类
                    new_ext = new_ext_class()
                    # 将扩展类加入到扩展集合中 这一步很重要,包含了扩展类的检测
                    self.add_extension(new_ext)
            except Exception as exception:
                LOG.warning(_LW("Extension file %(f)s wasn't loaded due to "
                                "%(exception)s"),
                            {'f': f, 'exception': exception})

    def add_extension(self, ext):
        # Do nothing if the extension doesn't check out
        # 检测扩展类
        if not self._check_extension(ext):
            return
        # 获取扩展类的别名
        alias = ext.get_alias()
        LOG.info(_LI('Loaded extension: %s'), alias)

        if alias in self.extensions:
            raise exceptions.DuplicatedExtension(alias=alias)
        # 将扩展加入到扩展字典中
        self.extensions[alias] = ext

     def _check_extension(self, extension):
        """Checks for required methods in extension objects."""
        # 检测扩展类 分别进行了get_name get_alias get_description get_updated检测
        # 意味着你写的扩展类一定要有这些方法,才能被成功载入
        try:
            LOG.debug('Ext name: %s', extension.get_name())
            LOG.debug('Ext alias: %s', extension.get_alias())
            LOG.debug('Ext description: %s', extension.get_description())
            LOG.debug('Ext updated: %s', extension.get_updated())
        except AttributeError:
            LOG.exception(_LE("Exception loading extension"))
            return False
        return isinstance(extension, ExtensionDescriptor)

3.3 加载扩展资源

# ext_mgr.extend_resources("2.0", attributes.RESOURCE_ATTRIBUTE_MAP)
# neutron/api/extensions.py
class ExtensionManager(object): 
    """Load extensions from the configured extension path.
       See tests/unit/extensions/foxinsocks.py for an
       example extension implementation.
    """
    def extend_resources(self, version, attr_map):
        """Extend resources with additional resources or attributes.

        :param attr_map: the existing mapping from resource name to
        attrs definition.

        After this function, we will extend the attr_map if an extension
        wants to extend this map.
        """
        processed_exts = {}
        # 获取3.3中载入的扩展的副本
        exts_to_process = self.extensions.copy()
        check_optionals = True
        # Iterate until there are unprocessed extensions or if no progress
        # is made in a whole iteration
        # 因为扩展与扩展之间有依赖,而程序并不知道加载的顺序,所以这里用了多个循环来
        # 按照正确的顺序加载扩展
        while exts_to_process:
            processed_ext_count = len(processed_exts)
            # 遍历扩展
            for ext_name, ext in list(exts_to_process.items()):
                # Process extension only if all required extensions
                # have been processed already
                # 如果所有依赖都被加载就继续运行,否则continue跳过
                required_exts_set = set(ext.get_required_extensions())
                if required_exts_set - set(processed_exts):
                    continue
                # 检测可选扩展
                optional_exts_set = set(ext.get_optional_extensions())
                if check_optionals and optional_exts_set - set(processed_exts):
                    continue
                # 获取对应版本的扩展属性图
                extended_attrs = ext.get_extended_resources(version)
                # 遍历扩展图 res:资源,resource_attr 资源拥有的属性
                for res, resource_attrs in six.iteritems(extended_attrs):
                    attr_map.setdefault(res, {}).update(resource_attrs)
                processed_exts[ext_name] = ext
                # 加载成功后删除该扩展
                del exts_to_process[ext_name]
            if len(processed_exts) == processed_ext_count:
                # if we hit here, it means there are unsatisfied
                # dependencies. try again without optionals since optionals
                # are only necessary to set order if they are present.
                if check_optionals:
                    check_optionals = False
                    continue
                # Exit loop as no progress was made
                break
        # 如果有扩展没有被成功加载就抛出异常
        if exts_to_process:
            unloadable_extensions = set(exts_to_process.keys())
            LOG.error(_LE("Unable to process extensions (%s) because "
                          "the configured plugins do not satisfy "
                          "their requirements. Some features will not "
                          "work as expected."),
                      ', '.join(unloadable_extensions))
            # Fail gracefully for default extensions, just in case some out
            # of tree plugins are not entirely up to speed
            default_extensions = set(const.DEFAULT_SERVICE_PLUGINS.values())
            if not unloadable_extensions <= default_extensions:
                raise exceptions.ExtensionsNotFound(
                    extensions=list(unloadable_extensions))

        # 扩展 每个扩展的属性图,调用每个扩展的update_attributes_map方法.
        for ext in processed_exts.values():
            ext.update_attributes_map(attr_map)

到这里server程序启动成功,还有部分代码未分析,持续更新

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 160,504评论 4 365
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,898评论 1 300
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 110,218评论 0 248
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,322评论 0 214
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,693评论 3 290
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,812评论 1 223
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 32,010评论 2 315
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,747评论 0 204
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,476评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,700评论 2 251
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,190评论 1 262
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,541评论 3 258
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,206评论 3 240
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,129评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,903评论 0 199
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,894评论 2 283
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,748评论 2 274

推荐阅读更多精彩内容

  • Swift版本点击这里欢迎加入QQ群交流: 594119878最新更新日期:18-09-17 About A cu...
    ylgwhyh阅读 24,877评论 7 249
  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 170,584评论 25 707
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,105评论 18 139
  • 发现 关注 消息 iOS 第三方库、插件、知名博客总结 作者大灰狼的小绵羊哥哥关注 2017.06.26 09:4...
    肇东周阅读 11,631评论 4 59
  • 第一次来到简书。 好久没写东西了,书也很少看。总想拿出笔写些什么的时候,脑子里就乱成一团浆糊,看来不进则退的事...
    箜篌丿阅读 251评论 0 0